-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Enable license feature usage for Security Realms #61963
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
Conversation
Changes the way that license checking for realms is performed so that it is compatible with feature usage reporting. The detailed changes are: 1. Replace the old license features of "ALL_REALMS" and "STANDARD_REALMS" with a new feature per realm type (+"CUSTOM") 2. When evaluating realms against the license, check the configured realms against their coresponding feature. We know only perform checks for realms that have been configured 3. Switched the previous license check that was performed in Realms.asList() to a new filtering process in a license state listener. This was needed due to the increased number of feature types that we were checking against. Because these checks are done on license change, the "last used" in the feature usage reflects the more recent of "node startup" or "license updated". This is not strictly accurate, since a realm might (and typically will) be used more frequently than that to perform authentications. However, for SAML, OIDC, and Delegated PKI the authentication flow uses specific Rest actions that do explicit license checking (primarily to provide client and explicit error messages) and these will report an accurate "last used" time. For LDAP, AD, PKI, Kerberos and Custom realms, we might consider a followup change to update the last-used time each time authenticated is successful.
|
Pinging @elastic/es-security (:Security/License) |
rjernst
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for picking it up so quickly!
ywangd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need read the tests more closely. But here are a few comments to begin with.
| SECURITY_AUTHORIZATION_ENGINE(OperationMode.PLATINUM, true), | ||
| SECURITY_STATS_AND_HEALTH(OperationMode.MISSING, true), | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra blank line
| // All realms that were explicitly configured in the settings, some of these may not be enabled due to licensing | ||
| private final List<Realm> allConfiguredRealms; | ||
| // the default native realms to enable if no other realms are permitted under the current license | ||
| private final List<Realm> fallbackNativeRealms; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not strictly a problem of this PR since it is carried from the existing code. But can we use a different word other than "native"? It is pretty confusing since we do have an actual "native" realm. It might be good enough to just call it "fallbackRealms".
| if (ReservedRealm.TYPE.equals(type)) { | ||
| return XPackLicenseState.Feature.SECURITY; | ||
| } | ||
| return XPACK_TYPES.getOrDefault(type, XPackLicenseState.Feature.SECURITY_CUSTOM_REALM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It is a bit weird that a method of "Internal"Realms can check for custom realm type.
| } | ||
| this.allConfiguredRealms = initRealms(); | ||
| this.allConfiguredRealms.forEach(r -> r.initialize(this, licenseState)); | ||
| this.activeRealms = allConfiguredRealms; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not call recomputeActiveRealms here? Is it because licenseState may not be ready at this point? It also assumes that the first usage of activeRealms will be after the licenseState is ready (and listener invoked). Will it be possible that somehow an unlicensed realm is enabled because of racing condition?
ywangd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
A few more comments. Nothing major.
| verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest); | ||
| verify(realms).asList(); | ||
| verifyNoMoreInteractions(realms); | ||
| verify(realms, atLeastOnce()).asList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
realms.asList() is now called in the init() method. So should this be atLeast(2)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it? Testing how many times a method is called tends to be an anti-pattern. Why do we can if it was called once or twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concern is different here because realms.asList() is called once in init() as:
assertThat(this.realms.asList(), equalTo(realmList));So either we don't test for this interaction at all. Or we should test for at least twice because the first time is done as part of test setup and there is no piont to test it.
| verify(licenseState, atLeastOnce()).checkFeature(Feature.SECURITY); | ||
| verify(licenseState, atLeastOnce()).checkFeature(Feature.SECURITY_CUSTOM_REALM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took me a while to understand the purpose of these two checks: IIUC, they are the alternative to ensure the licenseState listener work. Since the listener is a protected method and the its downstream method isRealmTypeAvailable is static, so the interactions have to be checked like this. I think it could use some comments for the underlying logic to help future readers (future me included).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add comments, but the explanation isn't what you've described.
The purpose of this PR is to add these calls. This is how we track feature usage. So, the test verifies that they've been called.
| private static final Map<String, XPackLicenseState.Feature> XPACK_TYPES = Map.ofEntries( | ||
| Map.entry(NativeRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY), | ||
| Map.entry(FileRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY), | ||
| Map.entry(LdapRealmSettings.AD_TYPE, XPackLicenseState.Feature.SECURITY_AD_REALM), | ||
| Map.entry(LdapRealmSettings.LDAP_TYPE, XPackLicenseState.Feature.SECURITY_LDAP_REALM), | ||
| Map.entry(PkiRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY_PKI_REALM), | ||
| Map.entry(SamlRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY_SAML_REALM), | ||
| Map.entry(OpenIdConnectRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY_OIDC_REALM), | ||
| Map.entry(KerberosRealmSettings.TYPE, XPackLicenseState.Feature.SECURITY_KERBEROS_REALM) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than InternalRealms#getConfigurableRealmsTypes, is there any other reason for this map to not include the ReservedRealm? In other places where this map is used, e.g. isXPackRealm, it feels better if ReversedRealm is in it.
getConfigurableRealmsTypes seems to be the only exception. It is only used for tests as far as I can tell. Would it be better to remove ReservedRealm inside this method only instead of not having it in the Map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably. There's a lot that could be cleaned up here. I tried to keep this PR as self contained as possible and didn't want to change the meaning of XPACK_TYPES unnecessarily.
|
I'm going to hold this over to 7.11 |
|
@elasticmachine update branch |
|
I've dropped the |
Changes the way that license checking for realms is performed so that
it is compatible with feature usage reporting.
The detailed changes are:
"STANDARD_REALMS" with a new feature per realm type (+"CUSTOM")
realms against their coresponding feature. We now only perform
checks for realms that have been configured
Realms.asList() to a new filtering process in a license state
listener. This was needed due to the increased number of feature
types that we were checking against.
Because these checks are done on license change, the "last used" in
the feature usage reflects the more recent of "node startup" or
"license updated". This is not strictly accurate, since a realm might
(and typically will) be used more frequently than that to perform
authentications. However, for SAML, OIDC, and Delegated PKI the
authentication flow uses specific Rest actions that do explicit
license checking (primarily to provide client and explicit error
messages) and these will report an accurate "last used" time.
For LDAP, AD, PKI, Kerberos and Custom realms, we might consider a
followup change to update the last-used time each time authentication
is successful.
Relates: #59342