-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Remove deprecated Authentication#getLookedUpBy #91126
Conversation
This PR removes the deprecated Authentication#getLookedUpBy method. Its usages are replaced by either isRunAs or getEffectiveSubject#getRealm or their combinations depending on the actual context. Relates: elastic#88494
Pinging @elastic/es-security (Team:Security) |
assertThat(auth.getAuthenticatingSubject().getRealm(), sameInstance(auth.getAuthenticatingSubject().getRealm())); | ||
assertThat(auth.getLookedUpBy(), sameInstance(auth.getLookedUpBy())); | ||
assertThat(auth.getEffectiveSubject().getVersion(), sameInstance(auth.getEffectiveSubject().getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(auth.getAuthenticationType())); | ||
assertThat(auth.getAuthenticatingSubject().getMetadata(), sameInstance(auth.getAuthenticatingSubject().getMetadata())); | ||
assertThat(auth.getAuthenticatingSubject().getRealm(), sameInstance(authentication.getAuthenticatingSubject().getRealm())); | ||
assertThat(auth.getEffectiveSubject().getRealm(), sameInstance(authentication.getEffectiveSubject().getRealm())); | ||
assertThat(auth.getEffectiveSubject().getVersion(), sameInstance(authentication.getEffectiveSubject().getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(authentication.getAuthenticationType())); | ||
assertThat( | ||
auth.getAuthenticatingSubject().getMetadata(), | ||
sameInstance(authentication.getAuthenticatingSubject().getMetadata()) | ||
); |
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.
There are existing bugs in this code block. Some assertions were checking same auth
object against itself.
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.
Very strong assertions 😅
if (anonymousUser.enabled()) { | ||
final Authentication auth = responseRef.get().authentication(); | ||
final User authUser = auth.getEffectiveSubject().getUser(); | ||
assertThat(authUser.roles(), emptyArray()); | ||
assertThat(auth.getAuthenticatingSubject().getRealm(), sameInstance(auth.getAuthenticatingSubject().getRealm())); | ||
assertThat(auth.getLookedUpBy(), sameInstance(auth.getLookedUpBy())); | ||
assertThat(auth.getEffectiveSubject().getVersion(), sameInstance(auth.getEffectiveSubject().getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(auth.getAuthenticationType())); | ||
assertThat(auth.getAuthenticatingSubject().getMetadata(), sameInstance(auth.getAuthenticatingSubject().getMetadata())); | ||
} else { | ||
assertThat(responseRef.get().authentication(), sameInstance(authentication)); | ||
} | ||
// Anonymous roles should not be added which means there is no change to the authentication at all | ||
assertThat(responseRef.get().authentication(), sameInstance(authentication)); |
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 can be simplified to just ensure the authentication object does not change at all.
public RealmRef getLookedUpBy() { | ||
if (isRunAs()) { | ||
return effectiveSubject.getRealm(); | ||
} else { | ||
// retain the behaviour of returning null for lookup realm for if the authentication is not run-as | ||
return null; | ||
} |
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.
Because we use null
to indicate lookup failure, it means the method can return null
even when isRunAs
is true
. I think this behaviour is itself a tech debt. But I am leaving it for another day.
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. Sanity check on one of the changes (not blocking).
@@ -1627,7 +1628,7 @@ LogEntryBuilder withAuthentication(Authentication authentication) { | |||
} else { | |||
final Authentication.RealmRef authenticatedBy = authentication.getAuthenticatingSubject().getRealm(); | |||
if (authentication.isRunAs()) { | |||
final Authentication.RealmRef lookedUpBy = authentication.getLookedUpBy(); | |||
final Authentication.RealmRef lookedUpBy = authentication.getEffectiveSubject().getRealm(); |
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 know we're not introducing this behavior in this PR, but it's a little suspect that we handle nullability of "lookedUpBy" in some places but not in others. Just jotting this down as an observation, I don't think we should change existing behavior in this refactor.
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 a great observation! This turned out to be a bug! When run-as fails (because user does not exist) and authentication_success event is enabled for audit logging, the server throws a NPE
Cannot invoke "org.elasticsearch.xpack.core.security.authc.Authentication$RealmRef.getName()" because "lookedUpBy" is null
Since this is an existing bug, I'll have a separate PR to address this. Thanks for raising it.
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.
Glad I jotted it down! Thanks for following up and figuring out the NPE. Separate PR makes sense 👍
@@ -664,7 +664,8 @@ private void authorizeRunAs( | |||
final ActionListener<AuthorizationResult> listener | |||
) { | |||
final Authentication authentication = requestInfo.getAuthentication(); | |||
if (authentication.getLookedUpBy() == null) { | |||
assert authentication.isRunAs() : "authentication does not have a run-as"; | |||
if (authentication.getEffectiveSubject().getRealm() == null) { |
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 a slight change in business logic: if runAs
is false
, the condition here would be true
regardless of the value of authentication.getEffectiveSubject().getRealm()
before the change, whereas now this is not the case anymore (in production, where assertions are disabled). Just double-checking that this is intentional.
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 enclosing method is authorizeRunAs
which is called inside maybeAuthorizeRunAs
when the authentication is indeed a run-as. So the new assertion is basically making the existing invariant explicit and does not change existing logic.
That said, I will change the assertion message to something like
authentication must have run-as for run-as to be authorized
Hope it makes the intention clearer.
Alternatively, we can inline this method. But since it is private. I don't feel strong for it. What do you think?
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 missed the fact that isRunAs
is necessarily true here due to maybeAuthorizeRunAs
. I had only looked at it in isolation. I'm good to leave as is -- it's a private method and to anyone looking at the code as a whole (as opposed to just the diff in the PR), things will make sense. I don't think we should inline.
assertThat(auth.getAuthenticatingSubject().getRealm(), sameInstance(auth.getAuthenticatingSubject().getRealm())); | ||
assertThat(auth.getLookedUpBy(), sameInstance(auth.getLookedUpBy())); | ||
assertThat(auth.getEffectiveSubject().getVersion(), sameInstance(auth.getEffectiveSubject().getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(auth.getAuthenticationType())); | ||
assertThat(auth.getAuthenticatingSubject().getMetadata(), sameInstance(auth.getAuthenticatingSubject().getMetadata())); | ||
assertThat(auth.getAuthenticatingSubject().getRealm(), sameInstance(authentication.getAuthenticatingSubject().getRealm())); | ||
assertThat(auth.getEffectiveSubject().getRealm(), sameInstance(authentication.getEffectiveSubject().getRealm())); | ||
assertThat(auth.getEffectiveSubject().getVersion(), sameInstance(authentication.getEffectiveSubject().getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(authentication.getAuthenticationType())); | ||
assertThat( | ||
auth.getAuthenticatingSubject().getMetadata(), | ||
sameInstance(authentication.getAuthenticatingSubject().getMetadata()) | ||
); |
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.
Very strong assertions 😅
@@ -174,6 +178,8 @@ public void testShouldNotAddAnonymousRolesForApiKeyOrServiceAccount() { | |||
authentication = AuthenticationTestHelper.builder().serviceAccount().build(); | |||
} | |||
final User user = authentication.getEffectiveSubject().getUser(); | |||
// API key or service account have no named roles | |||
assertThat(user.roles(), emptyArray()); |
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.
👍
…xist When run-as fails because the target user does not exist, the authentication is created with a null lookup realm. It is then rejected at authorization time. But for authentication, it is treated as success. This can lead to NPE when auditing the authenticationSuccess event. This PR fixes the NPE by checking whether lookup realm is null before using it. Relates: elastic#91126 (comment)
#91171) When run-as fails because the target user does not exist, the authentication is created with a null lookup realm. It is then rejected at authorization time. But for authentication, it is treated as success. This can lead to NPE when auditing the authenticationSuccess event. This PR fixes the NPE by checking whether lookup realm is null before using it. Relates: #91126 (comment)
elastic#91171) When run-as fails because the target user does not exist, the authentication is created with a null lookup realm. It is then rejected at authorization time. But for authentication, it is treated as success. This can lead to NPE when auditing the authenticationSuccess event. This PR fixes the NPE by checking whether lookup realm is null before using it. Relates: elastic#91126 (comment)
#91171) (#91240) When run-as fails because the target user does not exist, the authentication is created with a null lookup realm. It is then rejected at authorization time. But for authentication, it is treated as success. This can lead to NPE when auditing the authenticationSuccess event. This PR fixes the NPE by checking whether lookup realm is null before using it. Relates: #91126 (comment)
This PR removes the deprecated Authentication#getLookedUpBy method. Its usages are replaced by either isRunAs or
getEffectiveSubject#getRealm or their combinations depending on the actual context.
Relates: #88494