-
Notifications
You must be signed in to change notification settings - Fork 25.1k
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 last usage of Authentication constructor from production code #85905
Remove last usage of Authentication constructor from production code #85905
Conversation
Authentication object has many internal logic on what values can or cannot be used together. Hence it is better to instantiate it using convenient methods that is tailored for specific situations instead of the free usage of constructors. This PR adds a new convenient method for incorporating anonymous roles so that all production usages of constructors are removed (other than by the class itself). The next step is to remove usages in test code and ultimately locking down how Authentication can be created. PS: Strictly speaking, the Authentication(StreamInput) constructor is still used in production code. But this one is special, less used and not convenient to use. So we can let it stay for now. In long term, we can also lock it down.
Pinging @elastic/es-security (Team:Security) |
This PR is another step towards locking down how Authentication object can be instantiated: It should be created using dedicated convenient methods instead of constructors. Production usage of constructors are mostly removed. But lots of test code still uses them. This PR replaces one form of the usage with the newly introduced test helper and removes the corresponding constructor. Relates: elastic#85590 Relates: elastic#85905
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 think we should be using the new method in the authorization logic as well.
It's good that we move the logic inside a method, and avoid calling the Authentication
constructor. It's even better if we reuse the method.
Something like the following:
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java
index 509f33c93a0..c174bbf095f 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java
@@ -11,12 +11,9 @@ import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.common.util.ArrayUtils;
-import org.elasticsearch.core.Nullable;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.core.security.authz.store.RoleReference;
import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection;
-import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.User;
import java.util.Map;
@@ -86,10 +83,10 @@ public class Subject {
return metadata;
}
- public RoleReferenceIntersection getRoleReferenceIntersection(@Nullable AnonymousUser anonymousUser) {
+ public RoleReferenceIntersection getRoleReferenceIntersection() {
switch (type) {
case USER:
- return buildRoleReferencesForUser(anonymousUser);
+ return buildRoleReferencesForUser();
case API_KEY:
return buildRoleReferencesForApiKey();
case SERVICE_ACCOUNT:
@@ -157,21 +154,8 @@ public class Subject {
+ '}';
}
- private RoleReferenceIntersection buildRoleReferencesForUser(AnonymousUser anonymousUser) {
- if (user.equals(anonymousUser)) {
- return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(user.roles()));
- }
- final String[] allRoleNames;
- if (anonymousUser == null || false == anonymousUser.enabled()) {
- allRoleNames = user.roles();
- } else {
- // TODO: should we validate enable status and length of role names on instantiation time of anonymousUser?
- if (anonymousUser.roles().length == 0) {
- throw new IllegalStateException("anonymous is only enabled when the anonymous user has roles");
- }
- allRoleNames = ArrayUtils.concat(user.roles(), anonymousUser.roles());
- }
- return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(allRoleNames));
+ private RoleReferenceIntersection buildRoleReferencesForUser() {
+ return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(user.roles()));
}
private RoleReferenceIntersection buildRoleReferencesForApiKey() {
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java
index bba75559dc2..8110316a064 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java
@@ -170,7 +170,8 @@ public class CompositeRolesStore {
}
public void getRoles(Authentication authentication, ActionListener<Tuple<Role, Role>> roleActionListener) {
- final AuthenticationContext authenticationContext = AuthenticationContext.fromAuthentication(authentication);
+ Authentication effectiveAuthentication = authentication.maybeAddAnonymousRoles(this.anonymousUser);
+ final AuthenticationContext authenticationContext = AuthenticationContext.fromAuthentication(effectiveAuthentication);
getRole(authenticationContext.getEffectiveSubject(), ActionListener.wrap(role -> {
if (authenticationContext.isRunAs()) {
getRole(
@@ -195,7 +196,7 @@ public class CompositeRolesStore {
assert false == User.isInternal(subject.getUser()) : "Internal user should not pass here";
- final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(anonymousUser);
+ final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection();
roleReferenceIntersection.buildRole(this::buildRoleFromRoleReference, roleActionListener);
}
@@ -345,7 +346,7 @@ public class CompositeRolesStore {
tryGetRoleDescriptorForInternalUser(subject).ifPresentOrElse(
roleDescriptor -> listener.onResponse(List.of(Set.of(roleDescriptor))),
() -> {
- final List<RoleReference> roleReferences = subject.getRoleReferenceIntersection(anonymousUser).getRoleReferences();
+ final List<RoleReference> roleReferences = subject.getRoleReferenceIntersection().getRoleReferences();
final GroupedActionListener<Set<RoleDescriptor>> groupedActionListener = new GroupedActionListener<>(
listener,
roleReferences.size()
What do you think? Was this the plan for a follow-up or there were other plans?
I intentionally left it out in this PR and will be raising it as a separate one. I talked about it during the sync but I may not have been clear. The initial plan was indeed to reuse this method in This made me thinking that dropping |
Okay, I'm content that you've got it on your radar. The premise is that the So, we need a method that does the "anonymous roles inject" that can be reused in the above 3 cases, which will influence its signature. I think it is a bonus, as I understand you're saying, if we manage to make this method such that it never has to be invoked anywhere else, in the future. My overall point is that the method that replaces the |
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 Albert. You summarized the usage nicely. Since the future change will remove the |
This PR is another step towards locking down how Authentication object can be instantiated: It should be created using dedicated convenient methods instead of constructors. Production usage of constructors are mostly removed. But lots of test code still uses them. This PR replaces one form of the usage with the newly introduced test helper and removes the corresponding constructor. Relates: #85590 Relates: #85905
This PR removes one constructor from Authentication and change the visibility of the other one to private. This means Authentication object must now be created using dedicated convenient methods, e.g. newRealmAuthentication. This approach helps maintain the internal logic of Authentication object more easily and correctly. It also allows further refactoring for Authentication internals easier. Technically, the constructor with StreamInput argument is still public. But this one is special enough that we can leave it for now and come back to it later if necessary. Relates: elastic#85905 Relates: elastic#86020 Relates: elastic#86054
This PR removes one constructor from Authentication and change the visibility of the other one to private. This means Authentication object must now be created using dedicated convenient methods, e.g. newRealmAuthentication. This approach helps maintain the internal logic of Authentication object more easily and correctly. It also allows further refactoring for Authentication internals easier. Technically, the constructor with StreamInput argument is still public. But this one is special enough that we can leave it for now and come back to it later if necessary. Relates: #85905 Relates: #86020 Relates: #86054
Authentication object has many internal logic on what values can or
cannot be used together. Hence it is better to instantiate it using
convenient methods that is tailored for specific situations instead of
the free usage of constructors.
This PR adds a new convenient method for incorporating anonymous roles
so that all production usages of constructors are removed (other than by
the class itself). The next step is to remove usages in test code and
ultimately locking down how Authentication can be created.
PS: Strictly speaking, the Authentication(StreamInput) constructor is
still used in production code. But this one is special, less used and
not convenient to use. So we can let it stay for now. In long term, we
can also lock it down.