Skip to content

Commit

Permalink
Report anonymous roles in authenticate response (#61355) (#61454)
Browse files Browse the repository at this point in the history
Report anonymous roles in response to "GET _security/_authenticate" API call when:
* Anonymous role is enabled
* User is not the anonymous user
* Credentials is not an API Key
  • Loading branch information
ywangd committed Aug 24, 2020
1 parent d1031fd commit f061511
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ private void checkAuthentication() throws IOException {
final Map<String, Object> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ Collection<Object> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AuthenticateRequest, AuthenticateResponse> {

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
Expand All @@ -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));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,7 +45,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<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
Expand All @@ -70,7 +71,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<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
Expand Down Expand Up @@ -98,10 +99,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<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
Expand All @@ -118,7 +121,35 @@ 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();
org.elasticsearch.common.collect.List.of(authUser.roles()).containsAll(
org.elasticsearch.common.collect.List.of(authentication.getUser().roles()));
org.elasticsearch.common.collect.List.of(authUser.roles()).containsAll(
org.elasticsearch.common.collect.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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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 {
Expand Down

0 comments on commit f061511

Please sign in to comment.