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:
parent
d1031fd928
commit
f0615113b6
|
@ -132,7 +132,8 @@ public class EnableSecurityOnBasicLicenseIT extends ESRestTestCase {
|
||||||
final Map<String, Object> auth = getAsMap("/_security/_authenticate");
|
final Map<String, Object> auth = getAsMap("/_security/_authenticate");
|
||||||
// From file realm, configured in build.gradle
|
// From file realm, configured in build.gradle
|
||||||
assertThat(ObjectPath.evaluate(auth, "username"), equalTo("security_test_user"));
|
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 {
|
private void checkAllowedWrite(String indexName) throws IOException {
|
||||||
|
|
|
@ -448,6 +448,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
|
||||||
final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(),
|
final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(),
|
||||||
scriptService);
|
scriptService);
|
||||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
|
components.add(anonymousUser);
|
||||||
final ReservedRealm reservedRealm = new ReservedRealm(environment, settings, nativeUsersStore,
|
final ReservedRealm reservedRealm = new ReservedRealm(environment, settings, nativeUsersStore,
|
||||||
anonymousUser, securityIndex.get(), threadPool);
|
anonymousUser, securityIndex.get(), threadPool);
|
||||||
final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(environment, client, clusterService,
|
final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(environment, client, clusterService,
|
||||||
|
|
|
@ -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.AuthenticateRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
|
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
|
||||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
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.SystemUser;
|
||||||
import org.elasticsearch.xpack.core.security.user.User;
|
import org.elasticsearch.xpack.core.security.user.User;
|
||||||
import org.elasticsearch.xpack.core.security.user.XPackUser;
|
import org.elasticsearch.xpack.core.security.user.XPackUser;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class TransportAuthenticateAction extends HandledTransportAction<AuthenticateRequest, AuthenticateResponse> {
|
public class TransportAuthenticateAction extends HandledTransportAction<AuthenticateRequest, AuthenticateResponse> {
|
||||||
|
|
||||||
private final SecurityContext securityContext;
|
private final SecurityContext securityContext;
|
||||||
|
private final AnonymousUser anonymousUser;
|
||||||
|
|
||||||
@Inject
|
@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);
|
super(AuthenticateAction.NAME, transportService, actionFilters, AuthenticateRequest::new);
|
||||||
this.securityContext = securityContext;
|
this.securityContext = securityContext;
|
||||||
|
this.anonymousUser = anonymousUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,7 +49,32 @@ public class TransportAuthenticateAction extends HandledTransportAction<Authenti
|
||||||
} else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) {
|
} else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) {
|
||||||
listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal"));
|
listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal"));
|
||||||
} else {
|
} 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.AuthenticateRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
|
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
|
||||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
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.ElasticUser;
|
||||||
import org.elasticsearch.xpack.core.security.user.KibanaUser;
|
import org.elasticsearch.xpack.core.security.user.KibanaUser;
|
||||||
import org.elasticsearch.xpack.core.security.user.SystemUser;
|
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 transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
||||||
mock(ActionFilters.class), securityContext);
|
mock(ActionFilters.class), securityContext, prepareAnonymousUser());
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<AuthenticateResponse> responseRef = 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 transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
||||||
mock(ActionFilters.class), securityContext);
|
mock(ActionFilters.class), securityContext, prepareAnonymousUser());
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -98,10 +99,12 @@ public class TransportAuthenticateActionTests extends ESTestCase {
|
||||||
SecurityContext securityContext = mock(SecurityContext.class);
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
when(securityContext.getAuthentication()).thenReturn(authentication);
|
when(securityContext.getAuthentication()).thenReturn(authentication);
|
||||||
when(securityContext.getUser()).thenReturn(user);
|
when(securityContext.getUser()).thenReturn(user);
|
||||||
|
|
||||||
|
final AnonymousUser anonymousUser = prepareAnonymousUser();
|
||||||
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService,
|
||||||
mock(ActionFilters.class), securityContext);
|
mock(ActionFilters.class), securityContext, anonymousUser);
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -118,7 +121,35 @@ public class TransportAuthenticateActionTests extends ESTestCase {
|
||||||
});
|
});
|
||||||
|
|
||||||
assertThat(responseRef.get(), notNullValue());
|
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());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,13 @@ public class RestAuthenticateActionTests extends SecurityIntegTestCase {
|
||||||
assertThat(objectPath.evaluate("lookup_realm.type").toString(), equalTo("file"));
|
assertThat(objectPath.evaluate("lookup_realm.type").toString(), equalTo("file"));
|
||||||
assertThat(objectPath.evaluate("authentication_type").toString(), equalTo("realm"));
|
assertThat(objectPath.evaluate("authentication_type").toString(), equalTo("realm"));
|
||||||
List<String> roles = objectPath.evaluate("roles");
|
List<String> roles = objectPath.evaluate("roles");
|
||||||
assertThat(roles.size(), is(1));
|
if (anonymousEnabled) {
|
||||||
assertThat(roles, contains(SecuritySettingsSource.TEST_ROLE));
|
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 {
|
public void testAuthenticateApiWithoutAuthentication() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue