KAFKA-18573: Add support for OAuth jwt-bearer grant type#19754
Conversation
Adding support for the "urn:ietf:params:oauth:grant-type:jwt-bearer" grant type (AKA "jwt-bearer"). Includes further refactoring of the existing OAuth layer and addition of generic JWT assertion layer that can be leveraged in the future. Co-Authored-By: Zachary Hamilton <77027819+zacharydhamilton@users.noreply.github.com>
|
A label of 'needs-attention' was automatically added to this PR in order to raise the |
|
A label of 'needs-attention' was automatically added to this PR in order to raise the |
| if (algorithm.equalsIgnoreCase(TOKEN_SIGNING_ALGORITHM_RS256)) { | ||
| return Signature.getInstance("SHA256withRSA"); | ||
| } else if (algorithm.equalsIgnoreCase(TOKEN_SIGNING_ALGORITHM_ES256)) { | ||
| return Signature.getInstance("SHA256withECDSA"); | ||
| } else { | ||
| throw new NoSuchAlgorithmException(String.format("Unsupported signing algorithm: %s", algorithm)); |
There was a problem hiding this comment.
To be future proof, could we support all JOSE hash lengths for RS and ES: RS256, RS384, RS512, ES256, ES384, ES512.
There was a problem hiding this comment.
Yes. I spoke with @emasab offline and he said he's OK postponing additional algorithms until a later release.
…ded a unit test for passphrases
| public static final String DEFAULT_CLIENT_SASL_OAUTHBEARER_JWT_VALIDATOR_CLASS = "org.apache.kafka.common.security.oauthbearer.ClientJwtValidator"; | ||
| public static final String SASL_OAUTHBEARER_JWT_VALIDATOR_CLASS_DOC = "<p>The fully-qualified class name of a <code>JwtValidator</code> implementation used to" | ||
| + " validate the JWT from the identity provider.</p>" | ||
| + "<p>The default configuration value represents a class that maintains backward compatibility with previous versions of" |
There was a problem hiding this comment.
do we want to refer to the "default validator" here instead of "default configuration value"?? (referring to the "default configuration value" is confusing, because there is really no config for the DefaultJwtValidator right? (only for DEFAULT_BROKER.. and DEFAULT_CLIENT...)
| + "<p>The default configuration value represents a class that maintains backward compatibility with previous versions of" | ||
| + " Apache Kafka. The default implementation uses the configuration to determine which concrete implementation to create." | ||
| + "<p>Other implementations that are provided include:</p>" | ||
| + "<ul>" |
There was a problem hiding this comment.
Doesn't look like we use tags in config docs like these elsewhere (I may be missing something), and it surely shows polluted with the literal tags when looking at the java doc for this var usages. Is this intentional?
There was a problem hiding this comment.
It is rare; there are only nine occurrences other than those in this PR. These two client configuration were my inspiration:
| + " included in the token request. If provided, it should match one or more scopes configured in the identity provider.</p>" | ||
| + "<p>" | ||
| + "The scope was previously stored as part of the <code>sasl.jaas.config</code> configuration with the key <code>scope</code>." | ||
| + " For backward compatibility, the <code>scope</code> JAAS option can still be used, but it is deprecated and will be removed in a future version." |
There was a problem hiding this comment.
sasl.jaas.config is not really marked as deprecated yet, right? Do we plan to do it as follow-up?
There was a problem hiding this comment.
sasl.jaas.config itself is not deprecated. Just the OAuth layer's use of JAAS options.
I'm not entirely sure how to deprecate JAAS options because they're not listed anywhere explicitly like configuration are 🤔
There was a problem hiding this comment.
I see, interesting case then. Should we then add a note on the docs of the sasl.jaas.config config itself? (so users know that the oauth options of it are deprecated and they should use the sasl.oauthbearer properties instead?
There was a problem hiding this comment.
This is missing (Do we want to add the note the sasl.jaas.config? about the deprecated options and how the new props will take precedence)
There was a problem hiding this comment.
There is a statement about that under that line:
Order of precedence:
sasl.oauthbearer.client.credentials.client.idfrom configurationclientIdfrom JAAS
Or are you referring to something else?
There was a problem hiding this comment.
I was referring to a note on the jaas.config doc, to add the note on the one being deprecated, not only the one being added
| * should be specified with a listener-based property: | ||
| * | ||
| * <pre> | ||
| * listener.name.<listener name>.oauthbearer.sasl.oauthbearer.jwt.retriever.class=org.apache.kafka.common.security.oauthbearer.JwtBearerJwtRetriever |
There was a problem hiding this comment.
uhm having unescaped "<listener name>" within the <pre> will probably mess it up?
(ha ha, I had to escape the tags myself on this comment to show properly)
| * @param file File containing the source data | ||
| * @param contents Data from file; could be zero length but not {@code null} | ||
| */ | ||
| T transform(File file, String contents); |
There was a problem hiding this comment.
seems redundant that we need to pass the File and the contents, is it needed? (or could we consider passing the file only, and retrieve the contents from it?)
There was a problem hiding this comment.
The File is used for metadata (size, last modified time, etc.) and the String is used for the actual contents.
Because CachedFile.snapshot() performs the file loading step and provides the result to transform(), it's less work for the different Transformer implementations.
…gs.java Co-authored-by: Lianet Magrans <98415067+lianetm@users.noreply.github.com>
…arer/DefaultJwtRetriever.java Co-authored-by: Lianet Magrans <98415067+lianetm@users.noreply.github.com>
…gs.java Co-authored-by: Lianet Magrans <98415067+lianetm@users.noreply.github.com>
…T_CLIENT_SASL_OAUTHBEARER_JWT_VALIDATOR_CLASS into DEFAULT_SASL_OAUTHBEARER_JWT_VALIDATOR_CLASS
| + " included in the token request. If provided, it should match one or more scopes configured in the identity provider.</p>" | ||
| + "<p>" | ||
| + "The scope was previously stored as part of the <code>sasl.jaas.config</code> configuration with the key <code>scope</code>." | ||
| + " For backward compatibility, the <code>scope</code> JAAS option can still be used, but it is deprecated and will be removed in a future version." |
There was a problem hiding this comment.
This is missing (Do we want to add the note the sasl.jaas.config? about the deprecated options and how the new props will take precedence)
| public static final String SASL_OAUTHBEARER_TOKEN_ENDPOINT_URL = "sasl.oauthbearer.token.endpoint.url"; | ||
| public static final String SASL_OAUTHBEARER_TOKEN_ENDPOINT_URL_DOC = "The URL for the OAuth/OIDC identity provider. If the URL is HTTP(S)-based, it is the issuer's token" | ||
| + " endpoint URL to which requests will be made to login based on the configuration in " + SASL_JAAS_CONFIG + ". If the URL is file-based, it" | ||
| + " endpoint URL to which requests will be made to login based on the configured <code>JwtRetriever</code>. If the URL is file-based, it" |
There was a problem hiding this comment.
should we reference the SASL_OAUTHBEARER_JWT_RETRIEVER_CLASS config here instead of the class name? Seeing the related config name is probably more helpful (similar to what was done before for the old config)
There was a problem hiding this comment.
Agreed. I made the change.
| Function<String, String> configValueGetter, | ||
| Function<String, String> jaasValueGetter) { | ||
| if (cu.containsKey(configName)) { | ||
| return configValueGetter.apply(configName); |
There was a problem hiding this comment.
did we consider logging a warn in case cu.containsKey(configName) && jou.containsKey(jaasName)?
Otherwise it will go silently that the user has 2 conflicting properties, and that the jaas oauth option will be ignored
There was a problem hiding this comment.
Added more logging in this case. I struggled with the wording a bit, so happy to take suggestions.
| } | ||
|
|
||
| @Override | ||
| public void configure(Map<String, ?> configs, String saslMechanism, List<AppConfigurationEntry> jaasConfigEntries) { |
There was a problem hiding this comment.
I don't see any test for this configure logic on the JwtBearerJwtRetriever, just checking it is coming in the following PR?
There was a problem hiding this comment.
I added a few unit tests for the configure().
| if (algorithm.equalsIgnoreCase(TOKEN_SIGNING_ALGORITHM_RS256)) { | ||
| return Signature.getInstance("SHA256withRSA"); | ||
| } else if (algorithm.equalsIgnoreCase(TOKEN_SIGNING_ALGORITHM_ES256)) { | ||
| return Signature.getInstance("SHA256withECDSA"); | ||
| } else { | ||
| throw new NoSuchAlgorithmException(String.format("Unsupported signing algorithm: %s", algorithm)); |
|
|
||
| public class JwtResponseParser { | ||
|
|
||
| private static final String[] JSON_PATHS = new String[] {"/access_token", "/id_token"}; |
There was a problem hiding this comment.
do we intend to use id_tokens? I was expecting only access_token since those are the ones intended for API authorization (but I could be missing why we are expecting to use ID tokens?)
There was a problem hiding this comment.
According to the specs, access_token is correct. However, in my testing with GCP, the access_token was not present and only id_token was there as a JWT. Let me perform my testing against GCP again and see if I can get more clarity.
There was a problem hiding this comment.
ok, let's get to the bottom of it, but we can take it as follow-up if it needs more investigation
There was a problem hiding this comment.
This will need some more investigation. I tested against GCP and, depending on the claims that I include in the assertion JWT, the response includes either access_token or id_token.
There was a problem hiding this comment.
According to the RFC it should return an access token, here it says:
This document defines how a JWT Bearer Token can be used to request
an access token when a client wishes to utilize an existing trust
relationship
so I think it's fine to look first for access_token and then for the id_token.
Adding support for the `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type (AKA `jwt-bearer`). Includes further refactoring of the existing OAuth layer and addition of generic JWT assertion layer that can be leveraged in the future. This constitutes the main piece of the JWT Bearer grant type support. Forthcoming commits/PRs will include improvements for both the `client_credentials` and `jwt-bearer` grant types in the following areas: * Integration test coverage (KAFKA-19153) * Unit test coverage (KAFKA-19308) * Top-level documentation (KAFKA-19152) * Improvements to and documentation for `OAuthCompatibilityTool` (KAFKA-19307) Reviewers: Manikumar Reddy <manikumar@confluent.io>, Lianet Magrans <lmagrans@confluent.io> --------- Co-authored-by: Zachary Hamilton <77027819+zacharydhamilton@users.noreply.github.com> Co-authored-by: Lianet Magrans <98415067+lianetm@users.noreply.github.com>
@Override
public void configure(Map<String, ?> configs, String saslMechanism, List<AppConfigurationEntry> jaasConfigEntries) {
ConfigurationUtils cu = new ConfigurationUtils(configs, saslMechanism);
List<String> expectedAudiencesList = cu.get(SASL_OAUTHBEARER_EXPECTED_AUDIENCE);
Set<String> expectedAudiences = expectedAudiencesList != null ? Set.copyOf(expectedAudiencesList) : null;
Integer clockSkew = cu.validateInteger(SASL_OAUTHBEARER_CLOCK_SKEW_SECONDS, false);
String expectedIssuer = cu.validateString(SASL_OAUTHBEARER_EXPECTED_ISSUER, false);
String scopeClaimName = cu.validateString(SASL_OAUTHBEARER_SCOPE_CLAIM_NAME);
String subClaimName = cu.validateString(SASL_OAUTHBEARER_SUB_CLAIM_NAME);
CloseableVerificationKeyResolver verificationKeyResolver = verificationKeyResolverOpt.orElseGet(
() -> VerificationKeyResolverFactory.get(configs, saslMechanism, jaasConfigEntries)
);Sorry for bumping this old PR :) Should we close |
Adding support for the
urn:ietf:params:oauth:grant-type:jwt-bearergrant type (AKA
jwt-bearer). Includes further refactoring of theexisting OAuth layer and addition of generic JWT assertion layer that
can be leveraged in the future.
This constitutes the main piece of the JWT Bearer grant type support.
Forthcoming commits/PRs will include improvements for both the
client_credentialsandjwt-bearergrant types in the followingareas:
OAuthCompatibilityTool(KAFKA-19307)
Reviewers: Manikumar Reddy manikumar@confluent.io, Lianet Magrans lmagrans@confluent.io, Chia-Ping Tsai chia7712@gmail.com