[ 
https://issues.apache.org/jira/browse/NIFI-8406?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Vijay Jammi updated NIFI-8406:
------------------------------
    Description: 
The current oidc client authentication methods (client_secret_post, 
client_secret_basic) require client credentials (client_secret) to be stored as 
plain text on the client's filesystem, which could also be inadvertently 
checked into source control system.

Due to these and other security considerations, we should be able to use 
assertions as client credentials for authenticating against the token endpoint. 

While using assertions an oidc client will include client_assertion and 
client_assertion_type parameters instead of passing the client_secret for 
authentication.

Details on the OAuth 2.0 specifications for client authentication using jwt and 
assertions can be found under
 # [RFC 7523, Section 2.2 (Using JWTs for Client 
Authentication)|https://tools.ietf.org/html/rfc7523#section-2.2] 
 # [RFC 7521 (Using Assertions for Client 
Authentication)|https://tools.ietf.org/html/rfc7521#section-4.2].

Summary of the changes needed for assertion based client authentication.
 * Generate a pair of private and public key which can be made available to 
Nifi via a keystore on the filesystem. 
 * Make the public key or cert available to the Authorization Server.     
Alternatively to allow key rotations, we could configure a JWKS URL within Nifi 
to allow the authorization server download the public key using the keyId (kid).
 * While building the token request against the token endpoint, need 
StandardOidcIdentityProvider to provide a way to build a private_key_jwt based 
client authentication in addition to the existing client_secret_basic, 
client_secret_post. 
{code:java}
if 
(ClientAuthenticationMethod.PRIVATE_KEY_JWT.equals(clientAuthenticationMethod)){
    
     clientAuthentication = new PrivateKeyJWT(clientId, tokenEndpoint, 
jwsAlgorithm, privateKey, keyId, null);
}else if 
(ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthenticationMethod))
 { 
     clientAuthentication = new ClientSecretPost(clientId, clientSecret);
} else { 
     clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
}{code}

 * Encode and sign the JWT token with the private key, conforming to RFC 7523, 
section 2.2 and pass the signed token within the client_assertion request 
parameter along with the client_assertion_type. 
{code:java}
POST https://<auth-host>:<port>/.../token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJraWQiOiJkMlY2LS1OUG....hTMfA&
grant_type=authorization_code&
code=9ba8d85b-...-c7a6fb6dae55
{code}

 * The Authorization Server will use the client's public key or cert to verify 
the signature of the JWT and issues the token

 

Nifi Token Exchange Process

When authentication within Nifi is enabled via OpenId Connect, a user is 
re-directed to the configured authorization endpoint (authorization server) via 
Nifi's oidc/request endpoint. The user upon successfully authenticating with 
the authorization server is directed back to Nifi's (oidc/callback endpoint) 
with a temporary authorization code. This authorization code is then exchanged 
for an ID Token by the OidcService via the Oidc Identity Provider 
(StandardOidcIdentityProvider - exchangeAuthorizationCode(authorizationGrant)). 
The identity provider requests this exchange by authenticating itself against 
the token endpoint of the authorization server and presenting the authorization 
grant. The authorization server authenticates the client, validates the 
authorization grant and upon successful validation it issues an access token to 
Nifi.

Currently, the exchangeAuthorizationCode(...) on StandardOidcIdentityProvider 
provides two methods of Client Authentication (client_secret_basic, 
client_secret_post) which are both based on the Authorization Server issuing a 
set of client credentials (client_secret) which are presented to the 
authorization server as client credentials during token exchange.

 
{code:java}
public String exchangeAuthorizationCode(final AuthorizationGrant 
authorizationGrant) throws IOException { ... // 1 - Build the client 
authentication using the clientId and clientSecret obtained during the 
registration of the client with the authorization server if 
(oidcProviderMetadata.getTokenEndpointAuthMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_POST))
 { clientAuthentication = new ClientSecretPost(clientId, clientSecret); } else 
{ clientAuthentication = new ClientSecretBasic(clientId, clientSecret); } // 2 
- Build the token endpoint request with the above client authentication 
TokenRequest request = new TokenRequest(tokenEndpoint, clientAuthentication, 
authorizationGrant); HTTPRequest tokenHttpRequest = request.toHTTPRequest(); 
... // 3 - Submit the token request and Get the token response TokenResponse 
response = OIDCTokenResponseParser.parse(tokenHttpRequest.send()); // 4 - Upon 
Successful Response get the ID Token and the User Identity if 
(response.indicatesSuccess()) { final OIDCTokenResponse oidcTokenResponse = 
(OIDCTokenResponse) response; final OIDCTokens oidcTokens = 
oidcTokenResponse.getOIDCTokens(); // validate the access token and parse the 
claims  final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, 
null); // attempt to extract the configured claim to access the user's 
identity; default is 'email' String identity = 
claimsSet.getStringClaim(properties.getOidcClaimIdentifyingUser()); ... } // 5 
- If User Identity could not extracted out of the IDToken, lookup the identity 
against the UserInfo endpoint if (StringUtils.isBlank(identity)) { ... final 
BearerAccessToken bearerAccessToken = oidcTokens.getBearerAccessToken(); ... // 
7 - Invoke the UserInfo endpoint to lookup user identity against the UserInfo 
endpoint identity = lookupIdentityInUserInfoModified(bearerAccessToken); ... } 
... // 8 - Convert into a Nifi jwt for retrieval later final 
LoginAuthenticationToken loginToken = new LoginAuthenticationToken(identity, 
identity, expiresIn, claimsSet.getIssuer().getValue()); return 
jwtService.generateSignedToken(loginToken);} {code}

  was:
The current oidc client authentication methods (client_secret_post, 
client_secret_basic) require client credentials (client_secret) to be stored as 
plain text on the client's filesystem, which could also be inadvertently 
checked into source control system.

Due to these and other security considerations, we should be able to use 
assertions as client credentials for authenticating against the token endpoint. 

While using assertions an oidc client will include client_assertion and 
client_assertion_type parameters instead of passing the client_secret for 
authentication.

Details on the OAuth 2.0 specifications for client authentication using jwt and 
assertions can be found under
 # [RFC 7523, Section 2.2 (Using JWTs for Client 
Authentication)|https://tools.ietf.org/html/rfc7523#section-2.2] 
 # [RFC 7521 (Using Assertions for Client 
Authentication)|https://tools.ietf.org/html/rfc7521#section-4.2].

Summary of the changes needed for assertion based client authentication.
 * Generate a pair of private and public key which can be made available to 
Nifi via a keystore on the filesystem. 
 * Make the public key or cert available to the Authorization Server.     
Alternatively to allow key rotations, we could configure a JWKS URL within Nifi 
to allow the authorization server download the public key using the keyId (kid).
 * While building the token request against the token endpoint, need 
StandardOidcIdentityProvider to provide a way to build a private_key_jwt based 
client authentication in addition to the existing client_secret_basic, 
client_secret_post. 
{code:java}
if 
(ClientAuthenticationMethod.PRIVATE_KEY_JWT.equals(clientAuthenticationMethod)){
    
     clientAuthentication = new PrivateKeyJWT(clientId, tokenEndpoint, 
jwsAlgorithm, privateKey, keyId, null);
}else if 
(ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthenticationMethod))
 { 
     clientAuthentication = new ClientSecretPost(clientId, clientSecret);
} else { 
     clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
}{code}

 * Encode and sign the JWT token with the private key, conforming to RFC 7523, 
section 2.2 and pass the signed token within the client_assertion request 
parameter along with the client_assertion_type. 
{code:java}
POST https://<auth-host>:<port>/.../token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJraWQiOiJkMlY2LS1OUG....hTMfA&
grant_type=authorization_code&
code=9ba8d85b-...-c7a6fb6dae55
{code}

 * The Authorization Server will use the client's public key or cert to verify 
the signature of the JWT and issues the token

 

Nifi Token Exchange Process

When authentication within Nifi is enabled via OpenId Connect, a user is 
re-directed to the configured authorization endpoint (authorization server) via 
Nifi's oidc/request endpoint. The user upon successfully authenticating with 
the authorization server is directed back to Nifi's (oidc/callback endpoint) 
with a temporary authorization code. This authorization code is then exchanged 
for an ID Token by the OidcService via the Oidc Identity Provider 
(StandardOidcIdentityProvider - exchangeAuthorizationCode(authorizationGrant)). 
The identity provider requests this exchange by authenticating itself against 
the token endpoint of the authorization server and presenting the authorization 
grant. The authorization server authenticates the client, validates the 
authorization grant and upon successful validation it issues an access token to 
Nifi.

Currently, the exchangeAuthorizationCode(...) on StandardOidcIdentityProvider 
provides two methods of Client Authentication (client_secret_basic, 
client_secret_post) which are both based on the Authorization Server issuing a 
set of client credentials (client_secret) which are presented to the 
authorization server as client credentials during token exchange.

 
{code:java}
public String exchangeAuthorizationCode(final AuthorizationGrant 
authorizationGrant) throws IOException {public String 
exchangeAuthorizationCode(final AuthorizationGrant authorizationGrant) throws 
IOException { ...
 // 1 - Build the client authentication using the clientId and clientSecret 
obtained during the registration of the client with the authorization server if 
(oidcProviderMetadata.getTokenEndpointAuthMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_POST))
 { clientAuthentication = new ClientSecretPost(clientId, clientSecret); } else 
{ clientAuthentication = new ClientSecretBasic(clientId, clientSecret); }
 // 2 - Build the token endpoint request with the above client authentication 
TokenRequest request = new TokenRequest(tokenEndpoint, clientAuthentication, 
authorizationGrant); HTTPRequest tokenHttpRequest = request.toHTTPRequest();
 ...
 // 3 - Submit the token request and Get the token response TokenResponse 
response = OIDCTokenResponseParser.parse(tokenHttpRequest.send());
 // 4 - Upon Successful Response get the ID Token and the User Identity if 
(response.indicatesSuccess()) { final OIDCTokenResponse oidcTokenResponse = 
(OIDCTokenResponse) response; final OIDCTokens oidcTokens = 
oidcTokenResponse.getOIDCTokens();  // validate the access token and parse the 
claims  final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, 
null);  // attempt to extract the configured claim to access the user's 
identity; default is 'email' String identity = 
claimsSet.getStringClaim(properties.getOidcClaimIdentifyingUser()); ... } // 5 
- If User Identity could not extracted out of the IDToken, lookup the identity 
against the UserInfo endpoint if (StringUtils.isBlank(identity)) { ... final 
BearerAccessToken bearerAccessToken = oidcTokens.getBearerAccessToken(); ...  
// 7 - Invoke the UserInfo endpoint to lookup user identity against the 
UserInfo endpoint identity = 
lookupIdentityInUserInfoModified(bearerAccessToken);  ...  } ...  // 8 - 
Convert into a Nifi jwt for retrieval later final LoginAuthenticationToken 
loginToken = new LoginAuthenticationToken(identity, identity, expiresIn, 
claimsSet.getIssuer().getValue()); return 
jwtService.generateSignedToken(loginToken);}
{code}
 


> Oidc Identity Provider should support assertions as client credentials for 
> authenticating against the token endpoint
> --------------------------------------------------------------------------------------------------------------------
>
>                 Key: NIFI-8406
>                 URL: https://issues.apache.org/jira/browse/NIFI-8406
>             Project: Apache NiFi
>          Issue Type: Improvement
>          Components: Core Framework, Security
>    Affects Versions: 1.11.4
>            Reporter: Vijay Jammi
>            Priority: Major
>              Labels: OIDC, Security, assertion
>
> The current oidc client authentication methods (client_secret_post, 
> client_secret_basic) require client credentials (client_secret) to be stored 
> as plain text on the client's filesystem, which could also be inadvertently 
> checked into source control system.
> Due to these and other security considerations, we should be able to use 
> assertions as client credentials for authenticating against the token 
> endpoint. 
> While using assertions an oidc client will include client_assertion and 
> client_assertion_type parameters instead of passing the client_secret for 
> authentication.
> Details on the OAuth 2.0 specifications for client authentication using jwt 
> and assertions can be found under
>  # [RFC 7523, Section 2.2 (Using JWTs for Client 
> Authentication)|https://tools.ietf.org/html/rfc7523#section-2.2] 
>  # [RFC 7521 (Using Assertions for Client 
> Authentication)|https://tools.ietf.org/html/rfc7521#section-4.2].
> Summary of the changes needed for assertion based client authentication.
>  * Generate a pair of private and public key which can be made available to 
> Nifi via a keystore on the filesystem. 
>  * Make the public key or cert available to the Authorization Server.     
> Alternatively to allow key rotations, we could configure a JWKS URL within 
> Nifi to allow the authorization server download the public key using the 
> keyId (kid).
>  * While building the token request against the token endpoint, need 
> StandardOidcIdentityProvider to provide a way to build a private_key_jwt 
> based client authentication in addition to the existing client_secret_basic, 
> client_secret_post. 
> {code:java}
> if 
> (ClientAuthenticationMethod.PRIVATE_KEY_JWT.equals(clientAuthenticationMethod)){
>     
>      clientAuthentication = new PrivateKeyJWT(clientId, tokenEndpoint, 
> jwsAlgorithm, privateKey, keyId, null);
> }else if 
> (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthenticationMethod))
>  { 
>      clientAuthentication = new ClientSecretPost(clientId, clientSecret);
> } else { 
>      clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
> }{code}
>  * Encode and sign the JWT token with the private key, conforming to RFC 
> 7523, section 2.2 and pass the signed token within the client_assertion 
> request parameter along with the client_assertion_type. 
> {code:java}
> POST https://<auth-host>:<port>/.../token HTTP/1.1
> Content-Type: application/x-www-form-urlencoded
> client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
> client_assertion=eyJraWQiOiJkMlY2LS1OUG....hTMfA&
> grant_type=authorization_code&
> code=9ba8d85b-...-c7a6fb6dae55
> {code}
>  * The Authorization Server will use the client's public key or cert to 
> verify the signature of the JWT and issues the token
>  
> Nifi Token Exchange Process
> When authentication within Nifi is enabled via OpenId Connect, a user is 
> re-directed to the configured authorization endpoint (authorization server) 
> via Nifi's oidc/request endpoint. The user upon successfully authenticating 
> with the authorization server is directed back to Nifi's (oidc/callback 
> endpoint) with a temporary authorization code. This authorization code is 
> then exchanged for an ID Token by the OidcService via the Oidc Identity 
> Provider (StandardOidcIdentityProvider - 
> exchangeAuthorizationCode(authorizationGrant)). The identity provider 
> requests this exchange by authenticating itself against the token endpoint of 
> the authorization server and presenting the authorization grant. The 
> authorization server authenticates the client, validates the authorization 
> grant and upon successful validation it issues an access token to Nifi.
> Currently, the exchangeAuthorizationCode(...) on StandardOidcIdentityProvider 
> provides two methods of Client Authentication (client_secret_basic, 
> client_secret_post) which are both based on the Authorization Server issuing 
> a set of client credentials (client_secret) which are presented to the 
> authorization server as client credentials during token exchange.
>  
> {code:java}
> public String exchangeAuthorizationCode(final AuthorizationGrant 
> authorizationGrant) throws IOException { ... // 1 - Build the client 
> authentication using the clientId and clientSecret obtained during the 
> registration of the client with the authorization server if 
> (oidcProviderMetadata.getTokenEndpointAuthMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_POST))
>  { clientAuthentication = new ClientSecretPost(clientId, clientSecret); } 
> else { clientAuthentication = new ClientSecretBasic(clientId, clientSecret); 
> } // 2 - Build the token endpoint request with the above client 
> authentication TokenRequest request = new TokenRequest(tokenEndpoint, 
> clientAuthentication, authorizationGrant); HTTPRequest tokenHttpRequest = 
> request.toHTTPRequest(); ... // 3 - Submit the token request and Get the 
> token response TokenResponse response = 
> OIDCTokenResponseParser.parse(tokenHttpRequest.send()); // 4 - Upon 
> Successful Response get the ID Token and the User Identity if 
> (response.indicatesSuccess()) { final OIDCTokenResponse oidcTokenResponse = 
> (OIDCTokenResponse) response; final OIDCTokens oidcTokens = 
> oidcTokenResponse.getOIDCTokens(); // validate the access token and parse the 
> claims  final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, 
> null); // attempt to extract the configured claim to access the user's 
> identity; default is 'email' String identity = 
> claimsSet.getStringClaim(properties.getOidcClaimIdentifyingUser()); ... } // 
> 5 - If User Identity could not extracted out of the IDToken, lookup the 
> identity against the UserInfo endpoint if (StringUtils.isBlank(identity)) { 
> ... final BearerAccessToken bearerAccessToken = 
> oidcTokens.getBearerAccessToken(); ... // 7 - Invoke the UserInfo endpoint to 
> lookup user identity against the UserInfo endpoint identity = 
> lookupIdentityInUserInfoModified(bearerAccessToken); ... } ... // 8 - Convert 
> into a Nifi jwt for retrieval later final LoginAuthenticationToken loginToken 
> = new LoginAuthenticationToken(identity, identity, expiresIn, 
> claimsSet.getIssuer().getValue()); return 
> jwtService.generateSignedToken(loginToken);} {code}



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to