Report anonymous roles in authenticate response (#61355) (#61454)

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
This commit is contained in:
Yang Wang 2020-08-24 14:51:44 +10:00 committed by GitHub
parent d1031fd928
commit f0615113b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 9 deletions

View File

@ -132,7 +132,8 @@ public class EnableSecurityOnBasicLicenseIT extends ESRestTestCase {
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 {

View File

@ -448,6 +448,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
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,

View File

@ -17,18 +17,24 @@ import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
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
@ -43,7 +49,32 @@ public class TransportAuthenticateAction extends HandledTransportAction<Authenti
} 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));
}
}
}
}

View File

@ -17,6 +17,7 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
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;
@ -44,7 +45,7 @@ public class TransportAuthenticateActionTests extends ESTestCase {
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<>();
@ -70,7 +71,7 @@ public class TransportAuthenticateActionTests extends ESTestCase {
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<>();
@ -98,10 +99,12 @@ public class TransportAuthenticateActionTests extends ESTestCase {
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<>();
@ -118,7 +121,35 @@ public class TransportAuthenticateActionTests extends ESTestCase {
});
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;
}
}

View File

@ -67,8 +67,13 @@ public class RestAuthenticateActionTests extends SecurityIntegTestCase {
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 {