Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patterns support for allowed subjects by the JWT realm #102426

Conversation

albertzaharovits
Copy link
Contributor

@albertzaharovits albertzaharovits commented Nov 21, 2023

This adds support for allowing JWT token sub claims with Lucene patterns and wildcards,
by introducing a new JWT realm setting allowed_subject_patterns that can be used
alongside the exist allowed_subjects realm setting.

@albertzaharovits albertzaharovits changed the title Permit regex patterns for JWT realm allowed_subjects setting Permit patterns for JWT realm allowed_subjects setting Nov 23, 2023
@albertzaharovits albertzaharovits changed the title Permit patterns for JWT realm allowed_subjects setting Patterns for JWT realm allowed_subjects setting Nov 23, 2023
@albertzaharovits albertzaharovits changed the title Patterns for JWT realm allowed_subjects setting Patterns for JWT realm allowed_subject_patterns setting Nov 27, 2023
@albertzaharovits albertzaharovits changed the title Patterns for JWT realm allowed_subject_patterns setting Patterns support for allowed subjects by the JWT realm Nov 27, 2023
@albertzaharovits albertzaharovits added :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) >enhancement labels Nov 27, 2023
@elasticsearchmachine elasticsearchmachine added the Team:Security Meta label for security team label Nov 27, 2023
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-security (Team:Security)

@elasticsearchmachine
Copy link
Collaborator

Hi @albertzaharovits, I've created a changelog YAML for you.

Copy link
Contributor

@slobodanadamovic slobodanadamovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall functional changes are looking good. I left a few non-blocking comments for the code change. 👍

I still need to review tests and documentation changes. Just wanted to leave the initial feedback first.

) {
this.claimName = claimName;
this.fallbackClaimNames = fallbackClaimNames;
this.allowedClaimValues = allowedClaimValues;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The allowedClaimValues is not nullable anymore and will cause NPE when used below. We do validate it before constructing the validator, but maybe we should assert it as well. Same is for newly added allowedClaimValuePatterns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed the asserts, but in principal I prefer we annotate nullable parameters and assume the others cannot be null.

@@ -63,16 +85,14 @@ public void validate(JWSHeader jwsHeader, JWTClaimsSet jwtClaimsSet) {
if (claimValues == null) {
throw new IllegalArgumentException("missing required string claim [" + fallbackableClaim + "]");
}

if (allowedClaimValues != null && false == claimValues.stream().anyMatch(allowedClaimValues::contains)) {
if (false == claimValues.stream().anyMatch(allowedClaimValuesPredicate)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that we don't accept null anymore, but the null here was nicely short-circuiting the check to mean allow all and avoid any checks. Maybe we could do something similar and introduce a new private constructor which instead of allowedClaimValues and allowedClaimValuePatterns collections simply accepts allowedClaimValuesPredicate . This way we can create ALLOW_ALL_SUBJECTS which always returns true and thus avoid string matching against *.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought, this would not solve it completely. The proper way to short-circuit it would be if allowedClaimValuesPredicate accepted a list of strings to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The * used in the ALLOW_ALL_SUBJECTS benefits of some short-circuting of sorts:

} else if (pattern.equals("*")) {
return MATCH_ALL;
} else {

In the JwtStringClaimValidator constructor I could short-circuit the predicate building logic if either one of the "allowed" lists is empty, but I think that would complicate the logic for very little benefit (e.g. I expect the JVM will figure out that set stays empty and expedite the contains check).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't aware of a short-circuit in Automatons. Agreed, let's keep it simple and optimize if we need to.

);
}

private static void validateAllowedSubjectsSettings(RealmConfig realmConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: I don't feel strongly, but we could potentially move this settings validation to JwtRealmSetting class and implement a custom Setting.Validator for both allowed_subjects and allowed_subject_patterns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've pushed f095252 , but the "richness" of the error description decreased because it's not possible to tell if the dependent setting (of some other setting) is specified or not (validation has access to the default value in case it's not specified).
So now the error message only tells you that one of allowed_subjects or allowed_subject_patterns must be specified and not be empty.

docs/reference/security/authentication/jwt-realm.asciidoc Outdated Show resolved Hide resolved
docs/reference/security/authentication/jwt-realm.asciidoc Outdated Show resolved Hide resolved
`allowed_subject_patterns`::
Analogous to `allowed_subjects` but it accepts a list of <<regexp-syntax,Lucene regexp>>
and wildcards for the allowed JWT subjects. Wildcards use the `*` and `?` special
characters (which are escaped by `\`) to mean "any string" and "any single character"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: Would be nice to include a small example (similarly like you did for Lucene regex) that shows usage of both wildcards (including escaping).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed cc62b3b .

@@ -167,6 +180,253 @@ public void testDoesNotSupportWildcardOrRegex() throws ParseException {
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job on testing.
small nit: We could potentially add tests which demonstrate that:

  1. sub validation is case sensitive
  2. ? can be used as a single special character that must be present
  3. an empty ("") sub is rejected

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional test suggestion, we could adjust existing JwtRestIT integration test to randomly include allowed_subjects or allowed_subject_patterns (e.g. service_*@app?.example.com).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@slobodanadamovic slobodanadamovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍
Left a couple of non-blocking nits and suggestions. Feel free to address at your discretion.

Copy link
Contributor

@jakelandis jakelandis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

try {
return Automatons.predicate(patterns);
} catch (Exception e) {
throw new SettingsException("Invalid patterns for allowed claim values for [" + claimName + "].", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this also include the fallback sub if it is defined ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, if we go with also using patterns for more claims in the future. I'm not convinced for subjects only, today, it's warranted, and in any case, it's slightly messy to implement.
This error shows up when the allowed_subject_patterns is invalid, and it will show up in the logs as: "Invalid patterns for allowed claim values for [sub]." Even if the settings also contain something such as fallback_claims.sub: client_id I think the error is telling enough.

@albertzaharovits albertzaharovits merged commit f64bb49 into elastic:main Dec 5, 2023
19 checks passed
@albertzaharovits albertzaharovits deleted the JWT-regex-support-for-allowed-subject branch December 5, 2023 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>feature :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) Team:Security Meta label for security team v8.12.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants