diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 34ce70035a9e0..da818fdf520c1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -72,6 +72,9 @@ public class Authentication implements ToXContentObject { private final AuthenticationType type; private final Map metadata; // authentication contains metadata, includes api_key details (including api_key metadata) + private Subject authenticatingSubject; + private Subject effectiveSubject; + public Authentication(User user, RealmRef authenticatedBy, RealmRef lookedUpBy) { this(user, authenticatedBy, lookedUpBy, Version.CURRENT, AuthenticationType.REALM, Collections.emptyMap()); } @@ -109,10 +112,44 @@ public Authentication(StreamInput in) throws IOException { this.assertDomainAssignment(); } + /** + * Get the {@link Subject} that performs the actual authentication. This normally means it provides a credentials. + */ + public Subject getAuthenticatingSubject() { + initializeSubjects(); + return authenticatingSubject; + } + + /** + * Get the {@link Subject} that the authentication effectively represents. It may not be the authenticating subject + * because the authentication subject can run-as another subject. + */ + public Subject getEffectiveSubject() { + initializeSubjects(); + return effectiveSubject; + } + + /** + * Whether the authentication contains a subject run-as another subject. That is, the authentication subject + * is different from the effective subject. + */ + public boolean isRunAs() { + initializeSubjects(); + return authenticatingSubject != effectiveSubject; + } + + /** + * Use {@code getEffectiveSubject().getUser()} instead. + */ + @Deprecated public User getUser() { return user; } + /** + * Use {@code getAuthenticatingSubject().getRealm()} instead. + */ + @Deprecated public RealmRef getAuthenticatedBy() { return authenticatedBy; } @@ -124,7 +161,10 @@ public RealmRef getLookedUpBy() { /** * Get the realm where the effective user comes from. * The effective user is the es-security-runas-user if present or the authenticated user. + * + * Use {@code getEffectiveSubject().getRealm()} instead. */ + @Deprecated public RealmRef getSourceRealm() { return lookedUpBy == null ? authenticatedBy : lookedUpBy; } @@ -231,19 +271,18 @@ public boolean isAuthenticatedWithServiceAccount() { /** * Whether the authenticating user is an API key, including a simple API key or a token created by an API key. - * @return */ public boolean isAuthenticatedAsApiKey() { - final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getAuthenticatedBy().getType()); - assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getAuthenticatedBy().getName()); - return result; + initializeSubjects(); + return authenticatingSubject.getType() == Subject.Type.API_KEY; } - public boolean isAuthenticatedAnonymously() { + // TODO: this is not entirely accurate if anonymous user can create a token + private boolean isAuthenticatedAnonymously() { return AuthenticationType.ANONYMOUS.equals(getAuthenticationType()); } - public boolean isAuthenticatedInternally() { + private boolean isAuthenticatedInternally() { return AuthenticationType.INTERNAL.equals(getAuthenticationType()); } @@ -251,10 +290,8 @@ public boolean isAuthenticatedInternally() { * Authenticate with a service account and no run-as */ public boolean isServiceAccount() { - final boolean result = ServiceAccountSettings.REALM_TYPE.equals(getSourceRealm().getType()); - assert false == result || ServiceAccountSettings.REALM_NAME.equals(getSourceRealm().getName()) - : "service account realm name mismatch"; - return result; + initializeSubjects(); + return effectiveSubject.getType() == Subject.Type.SERVICE_ACCOUNT; } /** @@ -262,9 +299,8 @@ public boolean isServiceAccount() { * or a token created by the API key. */ public boolean isApiKey() { - final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getSourceRealm().getType()); - assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getSourceRealm().getName()) : "api key realm name mismatch"; - return result; + initializeSubjects(); + return effectiveSubject.getType() == Subject.Type.API_KEY; } /** @@ -321,10 +357,8 @@ public boolean canAccessResourcesOf(Authentication resourceCreatorAuthentication ).containsAll(EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType())) : "cross AuthenticationType comparison for canAccessResourcesOf is not applicable for: " + EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType()); - final AuthenticationContext myAuthContext = AuthenticationContext.fromAuthentication(this); - final AuthenticationContext creatorAuthContext = AuthenticationContext.fromAuthentication(resourceCreatorAuthentication); - final Subject mySubject = myAuthContext.getEffectiveSubject(); - final Subject creatorSubject = creatorAuthContext.getEffectiveSubject(); + final Subject mySubject = getEffectiveSubject(); + final Subject creatorSubject = resourceCreatorAuthentication.getEffectiveSubject(); return mySubject.canAccessResourcesOf(creatorSubject); } @@ -410,6 +444,21 @@ public void toXContentFragment(XContentBuilder builder) throws IOException { } } + private void initializeSubjects() { + if (authenticatingSubject == null) { + if (user.isRunAs()) { + authenticatingSubject = new Subject(user.authenticatedUser(), authenticatedBy, version, metadata); + // The lookup user for run-as currently don't have authentication metadata associated with them because + // lookupUser only returns the User object. The lookup user for authorization delegation does have + // authentication metadata, but the realm does not expose this difference between authenticatingUser and + // delegateUser so effectively this is handled together with the authenticatingSubject not effectiveSubject. + effectiveSubject = new Subject(user, lookedUpBy, version, Map.of()); + } else { + authenticatingSubject = effectiveSubject = new Subject(user, authenticatedBy, version, metadata); + } + } + } + private void assertApiKeyMetadata() { assert (false == isAuthenticatedAsApiKey()) || (this.metadata.get(AuthenticationField.API_KEY_ID_KEY) != null) : "API KEY authentication requires metadata to contain API KEY id, and the value must be non-null."; @@ -757,6 +806,7 @@ static boolean equivalentRealms(String name1, String type1, String name2, String } } + // TODO: Rename to AuthenticationMethod public enum AuthenticationType { REALM, API_KEY, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/AuthenticationContext.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/AuthenticationContext.java deleted file mode 100644 index f9a5ca926170e..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/AuthenticationContext.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.core.security.authc; - -import org.elasticsearch.Version; -import org.elasticsearch.xpack.core.security.user.User; - -import java.util.Map; - -import static org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; -import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; - -public class AuthenticationContext { - - private final Version version; - private final Subject authenticatingSubject; - private final Subject effectiveSubject; - // TODO: Rename to AuthenticationMethod - private final AuthenticationType type; - - private AuthenticationContext( - Version version, - Subject authenticatingSubject, - Subject effectiveSubject, - AuthenticationType authenticationType - ) { - this.version = version; - this.authenticatingSubject = authenticatingSubject; - this.effectiveSubject = effectiveSubject; - this.type = authenticationType; - } - - public boolean isRunAs() { - assert authenticatingSubject != null && effectiveSubject != null; - return authenticatingSubject != effectiveSubject; - } - - public Subject getAuthenticatingSubject() { - return authenticatingSubject; - } - - public Subject getEffectiveSubject() { - return effectiveSubject; - } - - public static AuthenticationContext fromAuthentication(Authentication authentication) { - final Builder builder = new Builder(authentication.getVersion()); - builder.authenticationType(authentication.getAuthenticationType()); - final User user = authentication.getUser(); - if (user.isRunAs()) { - builder.authenticatingSubject(user.authenticatedUser(), authentication.getAuthenticatedBy(), authentication.getMetadata()); - // The lookup user for run-as currently don't have authentication metadata associated with them because - // lookupUser only returns the User object. The lookup user for authorization delegation does have - // authentication metadata, but the realm does not expose this difference between authenticatingUser and - // delegateUser so effectively this is handled together with the authenticatingSubject not effectiveSubject. - builder.effectiveSubject(user, authentication.getLookedUpBy(), Map.of()); - } else { - builder.authenticatingSubject(user, authentication.getAuthenticatedBy(), authentication.getMetadata()); - } - return builder.build(); - } - - public static class Builder { - private final Version version; - private AuthenticationType authenticationType; - private Subject authenticatingSubject; - private Subject effectiveSubject; - - public Builder() { - this(Version.CURRENT); - } - - public Builder(Version version) { - this.version = version; - } - - public Builder authenticationType(AuthenticationType authenticationType) { - this.authenticationType = authenticationType; - return this; - } - - public Builder authenticatingSubject(User authenticatingUser, RealmRef authenticatingRealmRef, Map metadata) { - this.authenticatingSubject = new Subject(authenticatingUser, authenticatingRealmRef, version, metadata); - return this; - } - - public Builder effectiveSubject(User effectiveUser, RealmRef lookupRealmRef, Map metadata) { - this.effectiveSubject = new Subject(effectiveUser, lookupRealmRef, version, metadata); - return this; - } - - public AuthenticationContext build() { - if (effectiveSubject == null) { - effectiveSubject = authenticatingSubject; - } - return new AuthenticationContext(version, authenticatingSubject, effectiveSubject, authenticationType); - } - } - -} diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileDomainIntegTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileDomainIntegTests.java index b558ddcbb30c6..776d24c7b7232 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileDomainIntegTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileDomainIntegTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.xpack.core.security.action.user.PutUserAction; import org.elasticsearch.xpack.core.security.action.user.PutUserRequest; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationContext; import org.elasticsearch.xpack.core.security.authc.AuthenticationTests; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmDomain; @@ -143,7 +142,7 @@ public void testGetProfileByAuthenticationUnderDomain() { ), null ); - final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(); + final Subject subject = authentication.getEffectiveSubject(); // Profile does not exist yet final PlainActionFuture future1 = new PlainActionFuture<>(); @@ -189,7 +188,7 @@ public void testGetProfileByAuthenticationDomainless() { new Authentication.RealmRef(realmIdentifier1.getName(), realmIdentifier1.getType(), nodeName), null ); - final Subject subject1 = AuthenticationContext.fromAuthentication(authentication1).getEffectiveSubject(); + final Subject subject1 = authentication1.getEffectiveSubject(); final PlainActionFuture future1 = new PlainActionFuture<>(); profileService.searchVersionedDocumentForSubject(subject1, future1); @@ -206,7 +205,7 @@ public void testGetProfileByAuthenticationDomainless() { new Authentication.RealmRef(realmIdentifier2.getName(), realmIdentifier2.getType(), nodeName, realmDomain1), null ); - final Subject subject2 = AuthenticationContext.fromAuthentication(authentication2).getEffectiveSubject(); + final Subject subject2 = authentication2.getEffectiveSubject(); final PlainActionFuture future2 = new PlainActionFuture<>(); profileService.searchVersionedDocumentForSubject(subject2, future2); @@ -219,7 +218,7 @@ public void testGetProfileByAuthenticationDomainless() { new Authentication.RealmRef(realmIdentifier2.getName(), realmIdentifier2.getType(), nodeName), null ); - final Subject subject3 = AuthenticationContext.fromAuthentication(authentication3).getEffectiveSubject(); + final Subject subject3 = authentication3.getEffectiveSubject(); final PlainActionFuture future3 = new PlainActionFuture<>(); profileService.searchVersionedDocumentForSubject(subject3, future3); @@ -406,7 +405,7 @@ public void testProfileDocumentPassCanAccessResourceCheck() { authentication = authentication.token(); } } - final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(); + final Subject subject = authentication.getEffectiveSubject(); final ProfileService profileService = getInstanceFromRandomNode(ProfileService.class); final PlainActionFuture future1 = new PlainActionFuture<>(); profileService.activateProfile(authentication, future1); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/ApiKeyGenerator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/ApiKeyGenerator.java index 8b157a14b6631..619b6cbbc9c48 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/ApiKeyGenerator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/ApiKeyGenerator.java @@ -14,7 +14,6 @@ import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyRequest; import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationContext; import org.elasticsearch.xpack.core.security.authc.Subject; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.support.DLSRoleQueryValidator; @@ -54,7 +53,7 @@ public void generateApiKey(Authentication authentication, CreateApiKeyRequest re apiKeyService.createApiKey(authentication, request, roleDescriptors, listener); }, listener::onFailure); - final Subject effectiveSubject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(); + final Subject effectiveSubject = authentication.getEffectiveSubject(); // Retain current behaviour that User of an API key authentication has no roles if (effectiveSubject.getType() == Subject.Type.API_KEY) { 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 246a7a6d2bbec..9940513bc6a2a 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 @@ -26,7 +26,6 @@ import org.elasticsearch.core.Tuple; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationContext; import org.elasticsearch.xpack.core.security.authc.Subject; import org.elasticsearch.xpack.core.security.authz.RestrictedIndices; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; @@ -170,11 +169,10 @@ public void providersChanged() { } public void getRoles(Authentication authentication, ActionListener> roleActionListener) { - final AuthenticationContext authenticationContext = AuthenticationContext.fromAuthentication(authentication); - getRole(authenticationContext.getEffectiveSubject(), ActionListener.wrap(role -> { - if (authenticationContext.isRunAs()) { + getRole(authentication.getEffectiveSubject(), ActionListener.wrap(role -> { + if (authentication.isRunAs()) { getRole( - authenticationContext.getAuthenticatingSubject(), + authentication.getAuthenticatingSubject(), ActionListener.wrap( authenticatingRole -> roleActionListener.onResponse(new Tuple<>(role, authenticatingRole)), roleActionListener::onFailure diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java index 723cb7f64a3c7..85306fdf688dd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java @@ -56,7 +56,6 @@ import org.elasticsearch.xpack.core.security.action.profile.SearchProfilesResponse; import org.elasticsearch.xpack.core.security.action.profile.UpdateProfileDataRequest; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationContext; import org.elasticsearch.xpack.core.security.authc.Subject; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.support.SecurityIndexManager; @@ -116,7 +115,7 @@ public void getProfile(String uid, @Nullable Set dataKeys, ActionListene * to submit the request. */ public void activateProfile(Authentication authentication, ActionListener listener) { - final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(); + final Subject subject = authentication.getEffectiveSubject(); if (Subject.Type.USER != subject.getType()) { listener.onFailure( new IllegalArgumentException( diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 53d474a50a72f..98218b9093334 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -50,7 +50,6 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.core.security.authc.AuthenticationContext; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authc.Subject; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; @@ -1366,7 +1365,7 @@ public void testApiKeyAuthUsesApiKeyService() throws Exception { ); PlainActionFuture roleFuture = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(), roleFuture); + compositeRolesStore.getRole(authentication.getEffectiveSubject(), roleFuture); Role role = roleFuture.actionGet(); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); @@ -1440,7 +1439,7 @@ public void testApiKeyAuthUsesApiKeyServiceWithScopedRole() throws Exception { final String apiKeyId = (String) authentication.getMetadata().get(API_KEY_ID_KEY); PlainActionFuture roleFuture = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(), roleFuture); + compositeRolesStore.getRole(authentication.getEffectiveSubject(), roleFuture); Role role = roleFuture.actionGet(); assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE, mock(Authentication.class)), is(false)); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); @@ -1509,7 +1508,7 @@ public void testGetRolesForRunAs() { ) ); final PlainActionFuture future1 = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication1).getAuthenticatingSubject(), future1); + compositeRolesStore.getRole(authentication1.getAuthenticatingSubject(), future1); future1.actionGet(); verify(apiKeyService).parseRoleDescriptorsBytes(apiKeyId, limitedByRoleDescriptorBytes, RoleReference.ApiKeyRoleType.LIMITED_BY); @@ -1530,7 +1529,7 @@ public void testGetRolesForRunAs() { listener.onResponse(new RoleDescriptor(authenticatedUser2.principal(), null, null, null)); return null; }).when(serviceAccountService).getRoleDescriptorForPrincipal(eq(authenticatedUser2.principal()), anyActionListener()); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication2).getAuthenticatingSubject(), future2); + compositeRolesStore.getRole(authentication2.getAuthenticatingSubject(), future2); future2.actionGet(); verify(serviceAccountService).getRoleDescriptorForPrincipal(eq(authenticatedUser2.principal()), anyActionListener()); } @@ -1700,7 +1699,7 @@ public void testCacheEntryIsReusedForIdenticalApiKeyRoles() { ); PlainActionFuture roleFuture = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(), roleFuture); + compositeRolesStore.getRole(authentication.getEffectiveSubject(), roleFuture); roleFuture.actionGet(); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); verify(apiKeyService).parseRoleDescriptorsBytes("key-id-1", roleBytes, RoleReference.ApiKeyRoleType.ASSIGNED); @@ -1721,7 +1720,7 @@ public void testCacheEntryIsReusedForIdenticalApiKeyRoles() { metadata2 ); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(), roleFuture); + compositeRolesStore.getRole(authentication.getEffectiveSubject(), roleFuture); roleFuture.actionGet(); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); verify(apiKeyService, never()).parseRoleDescriptorsBytes(eq("key-id-2"), any(BytesReference.class), any()); @@ -1742,7 +1741,7 @@ public void testCacheEntryIsReusedForIdenticalApiKeyRoles() { metadata3 ); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.getRole(AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject(), roleFuture); + compositeRolesStore.getRole(authentication.getEffectiveSubject(), roleFuture); roleFuture.actionGet(); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); verify(apiKeyService).parseRoleDescriptorsBytes("key-id-3", anotherRoleBytes, RoleReference.ApiKeyRoleType.ASSIGNED);