diff --git a/x-pack/plugin/security/qa/basic-enable-security/src/test/java/org/elasticsearch/xpack/security/EnableSecurityOnBasicLicenseIT.java b/x-pack/plugin/security/qa/basic-enable-security/src/test/java/org/elasticsearch/xpack/security/EnableSecurityOnBasicLicenseIT.java index f3ce68e85a0fe..7d7b1d84a2bf5 100644 --- a/x-pack/plugin/security/qa/basic-enable-security/src/test/java/org/elasticsearch/xpack/security/EnableSecurityOnBasicLicenseIT.java +++ b/x-pack/plugin/security/qa/basic-enable-security/src/test/java/org/elasticsearch/xpack/security/EnableSecurityOnBasicLicenseIT.java @@ -132,7 +132,8 @@ private void checkAuthentication() throws IOException { final Map auth = getAsMap("/_security/_authenticate"); // From file realm, configured in build.gradle assertThat(ObjectPath.evaluate(auth, "username"), equalTo("security_test_user")); - assertThat(ObjectPath.evaluate(auth, "roles"), contains("security_test_role")); + // The anonymous role is granted by anonymous access enabled in build.gradle + assertThat(ObjectPath.evaluate(auth, "roles"), contains("security_test_role", "anonymous")); } private void checkAllowedWrite(String indexName) throws IOException { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 36f1fbf21be9b..155685aa23e35 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -448,6 +448,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(), scriptService); final AnonymousUser anonymousUser = new AnonymousUser(settings); + components.add(anonymousUser); final ReservedRealm reservedRealm = new ReservedRealm(environment, settings, nativeUsersStore, anonymousUser, securityIndex.get(), threadPool); final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(environment, client, clusterService, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateAction.java index a51717cf3a12d..db626e1b7a0e2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateAction.java @@ -17,18 +17,24 @@ import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest; import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.XPackUser; +import java.util.stream.Stream; + public class TransportAuthenticateAction extends HandledTransportAction { private final SecurityContext securityContext; + private final AnonymousUser anonymousUser; @Inject - public TransportAuthenticateAction(TransportService transportService, ActionFilters actionFilters, SecurityContext securityContext) { + public TransportAuthenticateAction(TransportService transportService, ActionFilters actionFilters, SecurityContext securityContext, + AnonymousUser anonymousUser) { super(AuthenticateAction.NAME, transportService, actionFilters, AuthenticateRequest::new); this.securityContext = securityContext; + this.anonymousUser = anonymousUser; } @Override @@ -43,7 +49,32 @@ protected void doExecute(Task task, AuthenticateRequest request, ActionListener< } else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) { listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal")); } else { - listener.onResponse(new AuthenticateResponse(authentication)); + final User user = authentication.getUser(); + final boolean shouldAddAnonymousRoleNames = anonymousUser.enabled() && false == anonymousUser.equals(user) + && authentication.getAuthenticationType() != Authentication.AuthenticationType.API_KEY; + if (shouldAddAnonymousRoleNames) { + final String[] allRoleNames = Stream.concat( + Stream.of(user.roles()), Stream.of(anonymousUser.roles())).toArray(String[]::new); + listener.onResponse(new AuthenticateResponse( + new Authentication( + new User(new User( + user.principal(), + allRoleNames, + user.fullName(), + user.email(), + user.metadata(), + user.enabled() + ), user.authenticatedUser()), + authentication.getAuthenticatedBy(), + authentication.getLookedUpBy(), + authentication.getVersion(), + authentication.getAuthenticationType(), + authentication.getMetadata() + ) + )); + } else { + listener.onResponse(new AuthenticateResponse(authentication)); + } } } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateActionTests.java index 135181d09f557..e35db6e794f92 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportAuthenticateActionTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest; import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; import org.elasticsearch.xpack.core.security.user.KibanaUser; import org.elasticsearch.xpack.core.security.user.SystemUser; @@ -24,6 +25,7 @@ import org.elasticsearch.xpack.core.security.user.XPackUser; import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.Matchers.containsString; @@ -44,7 +46,7 @@ public void testInternalUser() { TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, - mock(ActionFilters.class), securityContext); + mock(ActionFilters.class), securityContext, prepareAnonymousUser()); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -70,7 +72,7 @@ public void testNullUser() { TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, - mock(ActionFilters.class), securityContext); + mock(ActionFilters.class), securityContext, prepareAnonymousUser()); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -98,10 +100,12 @@ public void testValidAuthentication(){ SecurityContext securityContext = mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(authentication); when(securityContext.getUser()).thenReturn(user); + + final AnonymousUser anonymousUser = prepareAnonymousUser(); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, - mock(ActionFilters.class), securityContext); + mock(ActionFilters.class), securityContext, anonymousUser); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -118,7 +122,33 @@ public void onFailure(Exception e) { }); assertThat(responseRef.get(), notNullValue()); - assertThat(responseRef.get().authentication(), sameInstance(authentication)); + if (anonymousUser.enabled()) { + final Authentication auth = responseRef.get().authentication(); + final User authUser = auth.getUser(); + List.of(authUser.roles()).containsAll(List.of(authentication.getUser().roles())); + List.of(authUser.roles()).containsAll(List.of(anonymousUser.roles())); + assertThat(authUser.authenticatedUser(), sameInstance(user.authenticatedUser())); + assertThat(auth.getAuthenticatedBy(), sameInstance(auth.getAuthenticatedBy())); + assertThat(auth.getLookedUpBy(), sameInstance(auth.getLookedUpBy())); + assertThat(auth.getVersion(), sameInstance(auth.getVersion())); + assertThat(auth.getAuthenticationType(), sameInstance(auth.getAuthenticationType())); + assertThat(auth.getMetadata(), sameInstance(auth.getMetadata())); + } else { + assertThat(responseRef.get().authentication(), sameInstance(authentication)); + } assertThat(throwableRef.get(), nullValue()); } + + private AnonymousUser prepareAnonymousUser() { + final AnonymousUser anonymousUser = mock(AnonymousUser.class); + if (randomBoolean()) { + when(anonymousUser.enabled()).thenReturn(true); + when(anonymousUser.roles()).thenReturn( + randomList(1, 4, () -> randomAlphaOfLengthBetween(4, 12)).toArray(new String[0])); + } else { + when(anonymousUser.enabled()).thenReturn(false); + } + return anonymousUser; + } + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestAuthenticateActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestAuthenticateActionTests.java index e2d2e096d207d..871bcbe3be534 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestAuthenticateActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestAuthenticateActionTests.java @@ -67,8 +67,13 @@ public void testAuthenticateApi() throws Exception { assertThat(objectPath.evaluate("lookup_realm.type").toString(), equalTo("file")); assertThat(objectPath.evaluate("authentication_type").toString(), equalTo("realm")); List roles = objectPath.evaluate("roles"); - assertThat(roles.size(), is(1)); - assertThat(roles, contains(SecuritySettingsSource.TEST_ROLE)); + if (anonymousEnabled) { + assertThat(roles.size(), is(3)); + assertThat(roles, contains(SecuritySettingsSource.TEST_ROLE, SecuritySettingsSource.TEST_ROLE, "foo")); + } else { + assertThat(roles.size(), is(1)); + assertThat(roles, contains(SecuritySettingsSource.TEST_ROLE)); + } } public void testAuthenticateApiWithoutAuthentication() throws Exception {