Skip to content

Commit

Permalink
Fix workload identity (#3197)
Browse files Browse the repository at this point in the history
* Fix workload identity

https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation

* Add documentation

* Enhance uaa client authentication document
  • Loading branch information
strehle authored and duanemay committed Dec 19, 2024
1 parent b97cb9d commit 0a46c86
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 11 deletions.
46 changes: 36 additions & 10 deletions docs/UAA-Client-Authentication.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# UAA Client Authentication
UAA acts as OAuth2 / OIDC server and this requires the separation of users and clients. This document focuses on
the clients and in detail on the client authentication, because this has special behaviors.
In [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1) the password of a client is specified as so called client_secret. Its possession
UAA acts as OAuth2 / OIDC server and this requires the separate authentication of users and clients. This document focuses on
the clients and in detail on the key based client authentication, because this has special behaviors.
In [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1) the password of a client is specified as so-called secret (parameter client_secret). Its possession
or better the process of checking its possession means the authentication process.

The secrets can be passed to a server in different ways. It can happen through HTTP header and/or the body. In case of header the Authorization header is used, but
the encoding of the secret needs to be done according to the RFC 6749. UAA fixed this behavior with https://github.com/cloudfoundry/uaa/issues/778.
The OIDC standard defines more authentication mechanism, see [section 9](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication).
The usage of secrets via client_secret_basic and client_secret_post is easy to setup and easy to use, however if system to system communication is
The usage of secrets via client_secret_basic and client_secret_post is easy to set up and easy to use, however if system to system communication is
in use, this can get a security problem, because it will be hard to change secrets in running systems. The use of many secrets is not
supported, also because the check can only be done sequentially. The exchange of a secret is a security problem in it self. Therefore the newer
standards define further token based authentication mechanism for OAuth2 clients. They are:

* private_key_jwt [OIDC core standard](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication)
* private_key_jwt [OIDC core standard](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) and [RFC 7523 from OAuth2 standard](https://www.rfc-editor.org/info/rfc7523)
* tls_client_auth [RFC 8705](https://www.rfc-editor.org/rfc/rfc8705)

## New methods
The new methods are based on asymmetric trust relation, so that the keys are divided into a private and a public one. The private key should never leave
the original system, but only the public key should be exchanged.

### private_key_jwt (Experimental Feature)
### private_key_jwt (Partly finished)
The standard private_key_jwt is similar to the existing JWT bearer flow, but JWT bearer is for user principle propagation, whereas private_key_jwt
is used for client authentication only. The used technics are similar and therefore the trust model is similar. Both usages are specified in the same
[RFC 7523](https://www.rfc-editor.org/rfc/rfc7523.txt). The JWT bearer trust is based on parameters tokenKey and/or tokenKeyUrl parameter, part of the
Expand All @@ -28,13 +28,22 @@ of public keys and this set can contain many keys because each key has its own k
a dynamic token key URI. OIDC has defined the parameter jwks_uri for this already. The structure of the keys is defined with [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517).
UAA provides its own jwks_uri with endpoint /token_keys. The content of this endpoint is [JWKS](https://datatracker.ietf.org/doc/html/rfc7517#section-5).

The new parameters for UAA clients are:
The content of the JWT (parameter client_assertion) can be different. The difference is defined by the standards. The [OIDC core standard](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication)
simplify the structure, so that sub, iss content are the client_id of the authenticated OAuth2 client. The key rotation is supported with
jwks_uri, which retrieves the JWK. You can only have one JWKS_URI by the client. For the [RFC 7523 from OAuth2 standard](https://www.rfc-editor.org/info/rfc7523) the
structure is more complex but with seperated iss and sub there can be more than one entry of federated credential.

The new parameters for JWKS Trust in UAA clients are:

* jwks_uri
* jwks

This should allow a continuous trust between a UAA to UAA communication, e.g. using own UAA instances or within a UAA using different zones.

The new parameter for federated Credentials in UAA clients is (Work in progress parameter):

* fed_creds

### tls_client_auth (Planned Feature)
Not yet defined a release date.

Expand Down Expand Up @@ -70,14 +79,31 @@ oauth:
```
The example config above with jwks_uri enables continuous trust to a running uaa.
Here is a brief example of the oauth providers section, where UAA is acting as client.
```yaml
login:
oauth:
providers:
uaa.proxy:
type: oidc1.0
passwordGrantEnabled: true
discoveryUrl: http://localhost:8080/uaa/oauth/token/.well-known/openid-configuration
issuer: http://localhost:8080/uaa/oauth/token
linkText: Login with another UAA
relyingPartyId: uaa-trust-uri
jwtClientAuthentication: true
showLinkText: true
```
The option jwtClientAuthentication creates during the proxy flow a client assertion which is based on OIDC private_key_jwt.
### Developer implementation
As deverloper you should use the [UAA documention](https://docs.cloudfoundry.org/api/uaa/version/76.23.0/index.html#token). There is a description
As developer, you should use the [UAA documentation](https://docs.cloudfoundry.org/api/uaa/version/77.18.0/index.html#token). There is a description
about the new parameters client_assertion and client_assertion_type. In addition, you can check in the retrieved access_token tokens for the existence
of claim client_auth_method with value private_key_jwt, (client_auth_method=private_key). This claim should guarantee the used method of client
authentication. Tokens without this claim are authenticated with secrets. There might be use-cases where a stronger authentication mechanism is
required.
### Production use
The support of private_key_jwt for a production is planned with end of Q4/2023. The use for other purposes is given with a release of UAA starting
from version 76.23.0 or higher.
The support of private_key_jwt (according to OIDC) for a production is given with end of Q4/2024.
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,13 @@ public String getClientAssertion(
final boolean allowDynamicValueLookupInCustomZone
) {
HashMap<String, String> jwtClientConfiguration = Optional.ofNullable(getJwtClientConfigurationElements(config.getJwtClientAuthentication())).orElse(new HashMap<>());
String subject = readJwtClientOption(jwtClientConfiguration.get("sub"), config.getRelyingPartyId(), allowDynamicValueLookupInCustomZone);
String issuer = readJwtClientOption(jwtClientConfiguration.get("iss"), config.getRelyingPartyId(), allowDynamicValueLookupInCustomZone);
String audience = readJwtClientOption(jwtClientConfiguration.get("aud"), config.getTokenUrl().toString(), allowDynamicValueLookupInCustomZone);
String kid = readJwtClientOption(jwtClientConfiguration.get("kid"), keyInfoService.getActiveKey().keyId(), allowDynamicValueLookupInCustomZone);
Claims claims = new Claims();
claims.setAud(Arrays.asList(audience));
claims.setSub(config.getRelyingPartyId());
claims.setSub(subject);
claims.setIss(issuer);
claims.setJti(UUID.randomUUID().toString().replace("-", ""));
claims.setIat((int) Instant.now().minusSeconds(120).getEpochSecond());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ void createOAuthIdentityProvider() throws Exception {
"<ul><li> `kid` <small><mark>UAA 76.18.0</mark></small> Optional custom key from your defined keys, defaults to `activeKeyId` from token policy section</li>" +
"<li> `key` <small><mark>UAA 77.4.0</mark></small> Optional custom private key, used to generate the client JWT signature, defaults to key from token policy, depending on `kid` </li>" +
"<li> `cert` <small><mark>UAA 77.4.0</mark></small> Optional custom X509 certificate, related to key, used to generate the client JWT with x5t header, defaults to a cert from token policy or omits x5t header </li>" +
"<li> `sub` Optional custom subject, see RFC 7523, defaults to `relyingPartyId` for OIDC compliance</li>" +
"<li> `iss` Optional custom issuer, see RFC 7523, defaults to `relyingPartyId` for OIDC compliance</li>" +
"<li> `aud` Optional custom audience, see RFC 7523, defaults to `tokenUrl` for OIDC compliance</li></ul><p>" +
"The values in the list can be a reference to another section in uaa yaml, e.g. define for key a reference like ${\"jwt.client.key\"}. This will load the private key from yaml context jwt.client.key. The advantage is, that you can use a single key for many IdP configurations and the key itself is not persistent in the UAA DB.</p>"),
Expand Down Expand Up @@ -722,6 +723,7 @@ void createOidcIdentityProvider() throws Exception {
"<ul><li> `kid` <small><mark>UAA 76.18.0</mark></small> Optional custom key from your defined keys, defaults to `activeKeyId` from token policy section</li>" +
"<li> `key` <small><mark>UAA 77.4.0</mark></small> Optional custom private key, used to generate the client JWT signature, defaults to key from token policy, depending on `kid` </li>" +
"<li> `cert` <small><mark>UAA 77.4.0</mark></small> Optional custom X509 certificate, related to key, used to generate the client JWT with x5t header, defaults to a cert from token policy or omits x5t header </li>" +
"<li> `sub` Optional custom subject, see RFC 7523, defaults to `relyingPartyId` for OIDC compliance</li>" +
"<li> `iss` Optional custom issuer, see RFC 7523, defaults to `relyingPartyId` for OIDC compliance</li>" +
"<li> `aud` Optional custom audience, see RFC 7523, defaults to `tokenUrl` for OIDC compliance</li></ul><p>" +
"The values in the list can be a reference to another section in uaa yaml, e.g. define for key a reference like ${\"jwt.client.key\"}. This will load the private key from yaml context jwt.client.key. The advantage is, that you can use a single key for many IdP configurations and the key itself is not persistent in the UAA DB.</p>"),
Expand Down

0 comments on commit 0a46c86

Please sign in to comment.