Skip to content

Commit

Permalink
Fix inconsistency of internal user checking (#70123) (#70343)
Browse files Browse the repository at this point in the history
Most user related APIs explicilty prevent internal users from invoking them.
The list of internal users expands over time, with the most recent addition
being _async_search. An example of current problems is: unlike other internal
users, attempting to create a native user of _async_search would succeed
instead of resulting in a validation error. This PR improves the consistency
when it comes to check whether the given user or username is internal.
  • Loading branch information
ywangd authored Mar 12, 2021
1 parent be5e775 commit 3e13074
Show file tree
Hide file tree
Showing 15 changed files with 51 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ public static boolean isInternal(User user) {
return SystemUser.is(user) || XPackUser.is(user) || XPackSecurityUser.is(user) || AsyncSearchUser.is(user);
}

public static boolean isInternalUsername(String username) {
return SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username) || XPackSecurityUser.NAME.equals(username)
|| AsyncSearchUser.NAME.equals(username);
}

/** Write just the given {@link User}, but not the inner {@link #authenticatedUser}. */
private static void writeUser(User user, StreamOutput output) throws IOException {
output.writeBoolean(false); // not a system user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.junit.Before;
import org.junit.BeforeClass;
Expand Down Expand Up @@ -624,17 +627,18 @@ public void testOperationsOnReservedUsers() throws Exception {
() -> securityClient().preparePutUser(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, new SecureString(foobar), hasher).get());
assertThat(exception.getMessage(), containsString("user [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is anonymous"));

final String internalUser = randomFrom(SystemUser.NAME, XPackUser.NAME, XPackSecurityUser.NAME, AsyncSearchUser.NAME);
exception = expectThrows(IllegalArgumentException.class,
() -> securityClient().preparePutUser(SystemUser.NAME, new SecureString(foobar), hasher).get());
assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal"));
() -> securityClient().preparePutUser(internalUser, new SecureString(foobar), hasher).get());
assertThat(exception.getMessage(), containsString("user [" + internalUser + "] is internal"));

exception = expectThrows(IllegalArgumentException.class,
() -> securityClient().prepareChangePassword(SystemUser.NAME, foobar, hasher).get());
assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal"));
() -> securityClient().prepareChangePassword(internalUser, foobar, hasher).get());
assertThat(exception.getMessage(), containsString("user [" + internalUser + "] is internal"));

exception = expectThrows(IllegalArgumentException.class,
() -> securityClient().prepareDeleteUser(SystemUser.NAME).get());
assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal"));
() -> securityClient().prepareDeleteUser(internalUser).get());
assertThat(exception.getMessage(), containsString("user [" + internalUser + "] is internal"));

// get should work
GetUsersResponse response = securityClient().prepareGetUsers(username).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
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;

Expand All @@ -45,9 +43,9 @@ protected void doExecute(Task task, AuthenticateRequest request, ActionListener<
final User authUser = runAsUser == null ? null : runAsUser.authenticatedUser();
if (authUser == null) {
listener.onFailure(new ElasticsearchSecurityException("did not find an authenticated user"));
} else if (SystemUser.is(authUser) || XPackUser.is(authUser)) {
} else if (User.isInternal(authUser)) {
listener.onFailure(new IllegalArgumentException("user [" + authUser.principal() + "] is internal"));
} else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) {
} else if (User.isInternal(runAsUser)) {
listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal"));
} else {
final User user = authentication.getUser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;

public class TransportChangePasswordAction extends HandledTransportAction<ChangePasswordRequest, ActionResponse.Empty> {
Expand All @@ -42,7 +41,7 @@ protected void doExecute(Task task, ChangePasswordRequest request, ActionListene
if (AnonymousUser.isAnonymousUsername(username, settings)) {
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
return;
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
} else if (User.isInternalUsername(username)) {
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
import org.elasticsearch.xpack.core.security.action.user.DeleteUserResponse;
import org.elasticsearch.xpack.core.security.authc.esnative.ClientReservedRealm;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;

public class TransportDeleteUserAction extends HandledTransportAction<DeleteUserRequest, DeleteUserResponse> {
Expand All @@ -46,7 +45,7 @@ protected void doExecute(Task task, DeleteUserRequest request, final ActionListe
listener.onFailure(new IllegalArgumentException("user [" + username + "] is reserved and cannot be deleted"));
return;
}
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
} else if (User.isInternalUsername(username)) {
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import org.elasticsearch.xpack.core.security.action.user.GetUsersRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.core.security.authc.esnative.ClientReservedRealm;
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 org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;

Expand Down Expand Up @@ -57,7 +55,7 @@ protected void doExecute(Task task, final GetUsersRequest request, final ActionL
for (String username : requestedUsers) {
if (ClientReservedRealm.isReserved(username, settings)) {
realmLookup.add(username);
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
} else if (User.isInternalUsername(username)) {
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
return;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
import org.elasticsearch.xpack.core.security.authc.esnative.ClientReservedRealm;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;

import static org.elasticsearch.action.ValidateActions.addValidationError;
Expand Down Expand Up @@ -79,7 +77,7 @@ private ActionRequestValidationException validateRequest(PutUserRequest request)
validationException = addValidationError("user [" + username + "] is reserved and only the " +
"password can be changed", validationException);
}
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username) || XPackSecurityUser.NAME.equals(username)) {
} else if (User.isInternalUsername(username)) {
validationException = addValidationError("user [" + username + "] is internal", validationException);
} else {
Validation.Error usernameError = Validation.Users.validateUsername(username, true, settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import org.elasticsearch.xpack.core.security.action.user.SetEnabledAction;
import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequest;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;

/**
Expand Down Expand Up @@ -50,7 +49,7 @@ protected void doExecute(Task task, SetEnabledRequest request, ActionListener<Ac
if (securityContext.getUser().principal().equals(request.username())) {
listener.onFailure(new IllegalArgumentException("users may not update the enabled status of their own account"));
return;
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
} else if (User.isInternalUsername(username)) {
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
return;
} else if (AnonymousUser.isAnonymousUsername(username, settings)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@
import org.elasticsearch.xpack.core.security.authc.esnative.ClientReservedRealm;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.User.Fields;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;

import java.util.Arrays;
Expand Down Expand Up @@ -229,7 +227,7 @@ public void onFailure(Exception t) {
*/
public void changePassword(final ChangePasswordRequest request, final ActionListener<Void> listener) {
final String username = request.username();
assert SystemUser.NAME.equals(username) == false && XPackUser.NAME.equals(username) == false : username + "is internal!";
assert User.isInternalUsername(username) == false : username + "is internal!";
final String docType;
if (ClientReservedRealm.isReserved(username, settings)) {
docType = RESERVED_USER_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
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.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;

import java.util.Collections;
Expand All @@ -40,7 +42,8 @@ public class TransportAuthenticateActionTests extends ESTestCase {

public void testInternalUser() {
SecurityContext securityContext = mock(SecurityContext.class);
final Authentication authentication = new Authentication(randomFrom(SystemUser.INSTANCE, XPackUser.INSTANCE),
final Authentication authentication = new Authentication(randomFrom(SystemUser.INSTANCE, XPackUser.INSTANCE,
XPackSecurityUser.INSTANCE, AsyncSearchUser.INSTANCE),
new Authentication.RealmRef("native", "default_native", "node1"), null);
when(securityContext.getAuthentication()).thenReturn(authentication);
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.mockito.invocation.InvocationOnMock;
Expand Down Expand Up @@ -96,7 +98,8 @@ public void testInternalUsers() {
mock(ActionFilters.class), usersStore);
// Request will fail before the request hashing algorithm is checked, but we use the same algorithm as in settings for consistency
ChangePasswordRequest request = new ChangePasswordRequest();
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal(),
XPackSecurityUser.INSTANCE.principal(), AsyncSearchUser.INSTANCE.principal()));
request.passwordHash(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));

final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import org.elasticsearch.xpack.core.security.action.user.DeleteUserRequest;
import org.elasticsearch.xpack.core.security.action.user.DeleteUserResponse;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.mockito.invocation.InvocationOnMock;
Expand Down Expand Up @@ -81,7 +83,8 @@ public void testInternalUser() {
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ActionFilters.class),
usersStore, transportService);

DeleteUserRequest request = new DeleteUserRequest(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
DeleteUserRequest request = new DeleteUserRequest(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal(),
XPackSecurityUser.INSTANCE.principal(), AsyncSearchUser.INSTANCE.principal()));

final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import org.elasticsearch.xpack.core.security.action.user.GetUsersRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
Expand Down Expand Up @@ -133,7 +135,8 @@ public void testInternalUser() {
usersStore, transportService, mock(ReservedRealm.class));

GetUsersRequest request = new GetUsersRequest();
request.usernames(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
request.usernames(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal(),
XPackSecurityUser.INSTANCE.principal(), AsyncSearchUser.INSTANCE.principal()));

final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import org.elasticsearch.xpack.core.security.action.user.PutUserResponse;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
Expand Down Expand Up @@ -96,7 +98,8 @@ public void testSystemUser() {
TransportPutUserAction action = new TransportPutUserAction(Settings.EMPTY, mock(ActionFilters.class), usersStore, transportService);

PutUserRequest request = new PutUserRequest();
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal(),
XPackSecurityUser.INSTANCE.principal(), AsyncSearchUser.INSTANCE.principal()));

final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
final AtomicReference<PutUserResponse> responseRef = new AtomicReference<>();
Expand Down
Loading

0 comments on commit 3e13074

Please sign in to comment.