Reverse runAs user setup to store authenticated user inside runAs user (elastic/x-pack-elasticsearch#1371)

Original commit: elastic/x-pack-elasticsearch@8276662298
This commit is contained in:
Ryan Ernst 2017-05-09 13:49:14 -07:00 committed by GitHub
parent 590eea57ac
commit 1c3d907748
27 changed files with 734 additions and 268 deletions

View File

@ -42,6 +42,7 @@ import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.ml.MlMetadata; import org.elasticsearch.xpack.ml.MlMetadata;
import org.elasticsearch.xpack.ml.datafeed.DatafeedConfig; import org.elasticsearch.xpack.ml.datafeed.DatafeedConfig;
import org.elasticsearch.xpack.security.SecurityContext;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesAction; import org.elasticsearch.xpack.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesRequest; import org.elasticsearch.xpack.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse; import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse;
@ -193,6 +194,7 @@ public class PutDatafeedAction extends Action<PutDatafeedAction.Request, PutData
private final XPackLicenseState licenseState; private final XPackLicenseState licenseState;
private final Client client; private final Client client;
private final boolean securityEnabled; private final boolean securityEnabled;
private final SecurityContext securityContext;
@Inject @Inject
public TransportAction(Settings settings, TransportService transportService, public TransportAction(Settings settings, TransportService transportService,
@ -204,6 +206,7 @@ public class PutDatafeedAction extends Action<PutDatafeedAction.Request, PutData
this.licenseState = licenseState; this.licenseState = licenseState;
this.client = client; this.client = client;
this.securityEnabled = XPackSettings.SECURITY_ENABLED.get(settings); this.securityEnabled = XPackSettings.SECURITY_ENABLED.get(settings);
this.securityContext = securityEnabled ? new SecurityContext(settings, threadPool.getThreadContext()) : null;
} }
@Override @Override
@ -222,13 +225,13 @@ public class PutDatafeedAction extends Action<PutDatafeedAction.Request, PutData
// If security is enabled only create the datafeed if the user requesting creation has // If security is enabled only create the datafeed if the user requesting creation has
// permission to read the indices the datafeed is going to read from // permission to read the indices the datafeed is going to read from
if (securityEnabled) { if (securityEnabled) {
final String runAsUser = Authentication.getAuthentication(threadPool.getThreadContext()).getRunAsUser().principal(); final String username = securityContext.getUser().principal();
ActionListener<HasPrivilegesResponse> privResponseListener = ActionListener.wrap( ActionListener<HasPrivilegesResponse> privResponseListener = ActionListener.wrap(
r -> handlePrivsResponse(runAsUser, request, r, listener), r -> handlePrivsResponse(username, request, r, listener),
listener::onFailure); listener::onFailure);
HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); HasPrivilegesRequest privRequest = new HasPrivilegesRequest();
privRequest.username(runAsUser); privRequest.username(username);
privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); privRequest.clusterPrivileges(Strings.EMPTY_ARRAY);
// We just check for permission to use the search action. In reality we'll also // We just check for permission to use the search action. In reality we'll also
// use the scroll action, but that's considered an implementation detail. // use the scroll action, but that's considered an implementation detail.

View File

@ -63,16 +63,19 @@ public class SecurityContext {
*/ */
void setUser(User user, Version version) { void setUser(User user, Version version) {
Objects.requireNonNull(user); Objects.requireNonNull(user);
final Authentication.RealmRef authenticatedBy = new Authentication.RealmRef("__attach", "__attach", nodeName);
final Authentication.RealmRef lookedUpBy; final Authentication.RealmRef lookedUpBy;
if (user.runAs() == null) { if (user.isRunAs()) {
lookedUpBy = null; lookedUpBy = authenticatedBy;
} else { } else {
lookedUpBy = new Authentication.RealmRef("__attach", "__attach", nodeName); lookedUpBy = null;
} }
setAuthentication(new Authentication(user, authenticatedBy, lookedUpBy, version));
}
/** Writes the authentication to the thread context */
private void setAuthentication(Authentication authentication) {
try { try {
Authentication authentication =
new Authentication(user, new Authentication.RealmRef("__attach", "__attach", nodeName), lookedUpBy, version);
authentication.writeToContext(threadContext); authentication.writeToContext(threadContext);
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError("how can we have a IOException with a user we set", e); throw new AssertionError("how can we have a IOException with a user we set", e);
@ -80,7 +83,7 @@ public class SecurityContext {
} }
/** /**
* Runs the consumer in a new context as the provided user. The original constext is provided to the consumer. When this method * Runs the consumer in a new context as the provided user. The original context is provided to the consumer. When this method
* returns, the original context is restored. * returns, the original context is restored.
*/ */
public void executeAsUser(User user, Consumer<StoredContext> consumer, Version version) { public void executeAsUser(User user, Consumer<StoredContext> consumer, Version version) {
@ -90,4 +93,18 @@ public class SecurityContext {
consumer.accept(original); consumer.accept(original);
} }
} }
/**
* Runs the consumer in a new context after setting a new version of the authentication that is compatible with the version provided.
* The original context is provided to the consumer. When this method returns, the original context is restored.
*/
public void executeAfterRewritingAuthentication(Consumer<StoredContext> consumer, Version version) {
final StoredContext original = threadContext.newStoredContext(true);
final Authentication authentication = Objects.requireNonNull(getAuthentication());
try (ThreadContext.StoredContext ctx = threadContext.stashContext()) {
setAuthentication(new Authentication(authentication.getUser().authenticatedUser(), authentication.getAuthenticatedBy(),
authentication.getLookedUpBy(), version));
consumer.accept(original);
}
}
} }

View File

@ -34,16 +34,16 @@ public class TransportAuthenticateAction extends HandledTransportAction<Authenti
@Override @Override
protected void doExecute(AuthenticateRequest request, ActionListener<AuthenticateResponse> listener) { protected void doExecute(AuthenticateRequest request, ActionListener<AuthenticateResponse> listener) {
final User user = securityContext.getUser(); final User runAsUser = securityContext.getUser();
if (SystemUser.is(user) || XPackUser.is(user)) { final User authUser = runAsUser == null ? null : runAsUser.authenticatedUser();
listener.onFailure(new IllegalArgumentException("user [" + user.principal() + "] is internal")); if (authUser == null) {
return;
}
if (user == null) {
listener.onFailure(new ElasticsearchSecurityException("did not find an authenticated user")); listener.onFailure(new ElasticsearchSecurityException("did not find an authenticated user"));
return; } else if (SystemUser.is(authUser) || XPackUser.is(authUser)) {
listener.onFailure(new IllegalArgumentException("user [" + authUser.principal() + "] is internal"));
} else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) {
listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal"));
} else {
listener.onResponse(new AuthenticateResponse(runAsUser));
} }
listener.onResponse(new AuthenticateResponse(user));
} }
} }

View File

@ -56,13 +56,13 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
protected void doExecute(HasPrivilegesRequest request, ActionListener<HasPrivilegesResponse> listener) { protected void doExecute(HasPrivilegesRequest request, ActionListener<HasPrivilegesResponse> listener) {
final String username = request.username(); final String username = request.username();
final User runAsUser = Authentication.getAuthentication(threadPool.getThreadContext()).getRunAsUser(); final User user = Authentication.getAuthentication(threadPool.getThreadContext()).getUser();
if (runAsUser.principal().equals(username) == false) { if (user.principal().equals(username) == false) {
listener.onFailure(new IllegalArgumentException("users may only check the privileges of their own account")); listener.onFailure(new IllegalArgumentException("users may only check the privileges of their own account"));
return; return;
} }
authorizationService.roles(runAsUser, ActionListener.wrap( authorizationService.roles(user, ActionListener.wrap(
role -> checkPrivileges(request, role, listener), role -> checkPrivileges(request, role, listener),
listener::onFailure)); listener::onFailure));
} }

View File

@ -39,7 +39,7 @@ public class TransportSetEnabledAction extends HandledTransportAction<SetEnabled
protected void doExecute(SetEnabledRequest request, ActionListener<SetEnabledResponse> listener) { protected void doExecute(SetEnabledRequest request, ActionListener<SetEnabledResponse> listener) {
final String username = request.username(); final String username = request.username();
// make sure the user is not disabling themselves // make sure the user is not disabling themselves
if (Authentication.getAuthentication(threadPool.getThreadContext()).getRunAsUser().principal().equals(request.username())) { if (Authentication.getAuthentication(threadPool.getThreadContext()).getUser().principal().equals(request.username())) {
listener.onFailure(new IllegalArgumentException("users may not update the enabled status of their own account")); listener.onFailure(new IllegalArgumentException("users may not update the enabled status of their own account"));
return; return;
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) { } else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {

View File

@ -602,13 +602,14 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
msg.builder.field(Field.ACTION, action); msg.builder.field(Field.ACTION, action);
} }
if (user != null) { if (user != null) {
if (user.runAs() != null) { if (user.isRunAs()) {
if ("run_as_granted".equals(type) || "run_as_denied".equals(type)) { if ("run_as_granted".equals(type) || "run_as_denied".equals(type)) {
msg.builder.field(Field.PRINCIPAL, user.principal()); msg.builder.field(Field.PRINCIPAL, user.authenticatedUser().principal());
msg.builder.field(Field.RUN_AS_PRINCIPAL, user.runAs().principal()); msg.builder.field(Field.RUN_AS_PRINCIPAL, user.principal());
} else { } else {
msg.builder.field(Field.PRINCIPAL, user.runAs().principal()); // TODO: this doesn't make sense...
msg.builder.field(Field.RUN_BY_PRINCIPAL, user.principal()); msg.builder.field(Field.PRINCIPAL, user.principal());
msg.builder.field(Field.RUN_BY_PRINCIPAL, user.authenticatedUser().principal());
} }
} else { } else {
msg.builder.field(Field.PRINCIPAL, user.principal()); msg.builder.field(Field.PRINCIPAL, user.principal());
@ -691,9 +692,9 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
common("rest", type, msg.builder); common("rest", type, msg.builder);
if (user != null) { if (user != null) {
if (user.runAs() != null) { if (user.isRunAs()) {
msg.builder.field(Field.PRINCIPAL, user.runAs().principal()); msg.builder.field(Field.PRINCIPAL, user.principal());
msg.builder.field(Field.RUN_BY_PRINCIPAL, user.principal()); msg.builder.field(Field.RUN_BY_PRINCIPAL, user.authenticatedUser().principal());
} else { } else {
msg.builder.field(Field.PRINCIPAL, user.principal()); msg.builder.field(Field.PRINCIPAL, user.principal());
} }

View File

@ -350,8 +350,8 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail {
public void runAsGranted(User user, String action, TransportMessage message) { public void runAsGranted(User user, String action, TransportMessage message) {
if (events.contains(RUN_AS_GRANTED)) { if (events.contains(RUN_AS_GRANTED)) {
logger.info("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", logger.info("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]",
getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.principal(), getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.authenticatedUser().principal(),
user.runAs().principal(), action, message.getClass().getSimpleName()); user.principal(), action, message.getClass().getSimpleName());
} }
} }
@ -359,8 +359,8 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail {
public void runAsDenied(User user, String action, TransportMessage message) { public void runAsDenied(User user, String action, TransportMessage message) {
if (events.contains(RUN_AS_DENIED)) { if (events.contains(RUN_AS_DENIED)) {
logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]",
getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.principal(), getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.authenticatedUser().principal(),
user.runAs().principal(), action, message.getClass().getSimpleName()); user.principal(), action, message.getClass().getSimpleName());
} }
} }
@ -447,10 +447,11 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail {
static String principal(User user) { static String principal(User user) {
StringBuilder builder = new StringBuilder("principal=["); StringBuilder builder = new StringBuilder("principal=[");
if (user.runAs() != null) { builder.append(user.principal());
builder.append(user.runAs().principal()).append("], run_by_principal=["); if (user.isRunAs()) {
builder.append("], run_by_principal=[").append(user.authenticatedUser().principal());
} }
return builder.append(user.principal()).append("]").toString(); return builder.append("]").toString();
} }
public static void registerSettings(List<Setting<?>> settings) { public static void registerSettings(List<Setting<?>> settings) {

View File

@ -52,22 +52,6 @@ public class Authentication {
return user; return user;
} }
// TODO remove run as from the User object...
public User getRunAsUser() {
if (user.runAs() != null) {
return user.runAs();
}
return user;
}
/**
* returns true if this authentication represents a authentication object with a authenticated user that is different than the user the
* request should be run as
*/
public boolean isRunAs() {
return getUser().equals(getRunAsUser()) == false;
}
public RealmRef getAuthenticatedBy() { public RealmRef getAuthenticatedBy() {
return authenticatedBy; return authenticatedBy;
} }

View File

@ -324,13 +324,7 @@ public class AuthenticationService extends AbstractComponent {
Runnable action; Runnable action;
if (authentication != null) { if (authentication != null) {
try { action = () -> writeAuthToContext(authentication);
authentication.writeToContext(threadContext);
request.authenticationSuccess(authentication.getAuthenticatedBy().getName(), authentication.getUser());
action = () -> listener.onResponse(authentication);
} catch (Exception e) {
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
}
} else { } else {
action = () -> listener.onFailure(request.anonymousAccessDenied()); action = () -> listener.onFailure(request.anonymousAccessDenied());
} }
@ -365,8 +359,7 @@ public class AuthenticationService extends AbstractComponent {
} else { } else {
assert runAsUsername.isEmpty() : "the run as username may not be empty"; assert runAsUsername.isEmpty() : "the run as username may not be empty";
logger.debug("user [{}] attempted to runAs with an empty username", user.principal()); logger.debug("user [{}] attempted to runAs with an empty username", user.principal());
listener.onFailure(request.runAsDenied(new User(user.principal(), user.roles(), listener.onFailure(request.runAsDenied(new User(runAsUsername, null, user), authenticationToken));
new User(runAsUsername, Strings.EMPTY_ARRAY)), authenticationToken));
} }
} else { } else {
finishAuthentication(user); finishAuthentication(user);
@ -392,7 +385,14 @@ public class AuthenticationService extends AbstractComponent {
}, lookupUserListener::onFailure)); }, lookupUserListener::onFailure));
final IteratingActionListener<User, Realm> userLookupListener = final IteratingActionListener<User, Realm> userLookupListener =
new IteratingActionListener<>(ActionListener.wrap((lookupUser) -> userConsumer.accept(new User(user, lookupUser)), new IteratingActionListener<>(ActionListener.wrap((lookupUser) -> {
if (lookupUser == null) {
// the user does not exist, but we still create a User object, which will later be rejected by authz
userConsumer.accept(new User(runAsUsername, null, user));
} else {
userConsumer.accept(new User(lookupUser, user));
}
},
(e) -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken))), (e) -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken))),
realmLookupConsumer, realmsList, threadContext); realmLookupConsumer, realmsList, threadContext);
try { try {
@ -407,7 +407,8 @@ public class AuthenticationService extends AbstractComponent {
* one. If authentication is successful, this method also ensures that the authentication is written to the ThreadContext * one. If authentication is successful, this method also ensures that the authentication is written to the ThreadContext
*/ */
void finishAuthentication(User finalUser) { void finishAuthentication(User finalUser) {
if (finalUser.enabled() == false || (finalUser.runAs() != null && finalUser.runAs().enabled() == false)) { if (finalUser.enabled() == false || finalUser.authenticatedUser().enabled() == false) {
// TODO: these should be different log messages if the runas vs auth user is disabled?
logger.debug("user [{}] is disabled. failing authentication", finalUser); logger.debug("user [{}] is disabled. failing authentication", finalUser);
listener.onFailure(request.authenticationFailed(authenticationToken)); listener.onFailure(request.authenticationFailed(authenticationToken));
} else { } else {

View File

@ -133,7 +133,7 @@ public class AuthorizationService extends AbstractComponent {
setOriginatingAction(action); setOriginatingAction(action);
// first we need to check if the user is the system. If it is, we'll just authorize the system access // first we need to check if the user is the system. If it is, we'll just authorize the system access
if (SystemUser.is(authentication.getRunAsUser())) { if (SystemUser.is(authentication.getUser())) {
if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) { if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) {
setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL);
grant(authentication, action, request); grant(authentication, action, request);
@ -146,13 +146,13 @@ public class AuthorizationService extends AbstractComponent {
Role permission = userRole; Role permission = userRole;
// check if the request is a run as request // check if the request is a run as request
final boolean isRunAs = authentication.isRunAs(); final boolean isRunAs = authentication.getUser().isRunAs();
if (isRunAs) { if (isRunAs) {
// if we are running as a user we looked up then the authentication must contain a lookedUpBy. If it doesn't then this user // if we are running as a user we looked up then the authentication must contain a lookedUpBy. If it doesn't then this user
// doesn't really exist but the authc service allowed it through to avoid leaking users that exist in the system // doesn't really exist but the authc service allowed it through to avoid leaking users that exist in the system
if (authentication.getLookedUpBy() == null) { if (authentication.getLookedUpBy() == null) {
throw denyRunAs(authentication, action, request); throw denyRunAs(authentication, action, request);
} else if (permission.runAs().check(authentication.getRunAsUser().principal())) { } else if (permission.runAs().check(authentication.getUser().principal())) {
grantRunAs(authentication, action, request); grantRunAs(authentication, action, request);
permission = runAsRole; permission = runAsRole;
} else { } else {
@ -203,7 +203,7 @@ public class AuthorizationService extends AbstractComponent {
// we only want the xpack user to use the xpack delete by query action // we only want the xpack user to use the xpack delete by query action
if (XPackDeleteByQueryAction.NAME.equals(action) if (XPackDeleteByQueryAction.NAME.equals(action)
&& XPackUser.is(authentication.getRunAsUser()) == false) { && XPackUser.is(authentication.getUser()) == false) {
throw denial(authentication, action, request); throw denial(authentication, action, request);
} }
@ -230,7 +230,7 @@ public class AuthorizationService extends AbstractComponent {
} }
MetaData metaData = clusterService.state().metaData(); MetaData metaData = clusterService.state().metaData();
AuthorizedIndices authorizedIndices = new AuthorizedIndices(authentication.getRunAsUser(), permission, action, metaData); AuthorizedIndices authorizedIndices = new AuthorizedIndices(authentication.getUser(), permission, action, metaData);
Set<String> indexNames = resolveIndexNames(authentication, action, request, metaData, authorizedIndices); Set<String> indexNames = resolveIndexNames(authentication, action, request, metaData, authorizedIndices);
assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty"; assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty";
@ -247,13 +247,13 @@ public class AuthorizationService extends AbstractComponent {
throw denial(authentication, action, request); throw denial(authentication, action, request);
} else if (indicesAccessControl.getIndexPermissions(SecurityLifecycleService.SECURITY_INDEX_NAME) != null } else if (indicesAccessControl.getIndexPermissions(SecurityLifecycleService.SECURITY_INDEX_NAME) != null
&& indicesAccessControl.getIndexPermissions(SecurityLifecycleService.SECURITY_INDEX_NAME).isGranted() && indicesAccessControl.getIndexPermissions(SecurityLifecycleService.SECURITY_INDEX_NAME).isGranted()
&& XPackUser.is(authentication.getRunAsUser()) == false && XPackUser.is(authentication.getUser()) == false
&& MONITOR_INDEX_PREDICATE.test(action) == false && MONITOR_INDEX_PREDICATE.test(action) == false
&& isSuperuser(authentication.getRunAsUser()) == false) { && isSuperuser(authentication.getUser()) == false) {
// only the XPackUser is allowed to work with this index, but we should allow indices monitoring actions through for debugging // only the XPackUser is allowed to work with this index, but we should allow indices monitoring actions through for debugging
// purposes. These monitor requests also sometimes resolve indices concretely and then requests them // purposes. These monitor requests also sometimes resolve indices concretely and then requests them
logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]", logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]",
authentication.getRunAsUser().principal(), action, SecurityLifecycleService.SECURITY_INDEX_NAME); authentication.getUser().principal(), action, SecurityLifecycleService.SECURITY_INDEX_NAME);
throw denial(authentication, action, request); throw denial(authentication, action, request);
} else { } else {
setIndicesAccessControl(indicesAccessControl); setIndicesAccessControl(indicesAccessControl);
@ -380,7 +380,7 @@ public class AuthorizationService extends AbstractComponent {
return false; return false;
} }
final String username = usernames[0]; final String username = usernames[0];
final boolean sameUsername = authentication.getRunAsUser().principal().equals(username); final boolean sameUsername = authentication.getUser().principal().equals(username);
if (sameUsername && ChangePasswordAction.NAME.equals(action)) { if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
return checkChangePasswordAction(authentication); return checkChangePasswordAction(authentication);
} }
@ -396,7 +396,7 @@ public class AuthorizationService extends AbstractComponent {
// we need to verify that this user was authenticated by or looked up by a realm type that support password changes // we need to verify that this user was authenticated by or looked up by a realm type that support password changes
// otherwise we open ourselves up to issues where a user in a different realm could be created with the same username // otherwise we open ourselves up to issues where a user in a different realm could be created with the same username
// and do malicious things // and do malicious things
final boolean isRunAs = authentication.isRunAs(); final boolean isRunAs = authentication.getUser().isRunAs();
final String realmType; final String realmType;
if (isRunAs) { if (isRunAs) {
realmType = authentication.getLookedUpBy().getType(); realmType = authentication.getLookedUpBy().getType();
@ -429,19 +429,19 @@ public class AuthorizationService extends AbstractComponent {
} }
private ElasticsearchSecurityException denialException(Authentication authentication, String action) { private ElasticsearchSecurityException denialException(Authentication authentication, String action) {
final User user = authentication.getUser(); final User authUser = authentication.getUser().authenticatedUser();
// Special case for anonymous user // Special case for anonymous user
if (isAnonymousEnabled && anonymousUser.equals(user)) { if (isAnonymousEnabled && anonymousUser.equals(authUser)) {
if (anonymousAuthzExceptionEnabled == false) { if (anonymousAuthzExceptionEnabled == false) {
throw authcFailureHandler.authenticationRequired(action, threadContext); throw authcFailureHandler.authenticationRequired(action, threadContext);
} }
} }
// check for run as // check for run as
if (user != authentication.getRunAsUser()) { if (authentication.getUser().isRunAs()) {
return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(), return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, authUser.principal(),
authentication.getRunAsUser().principal()); authentication.getUser().principal());
} }
return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal()); return authorizationError("action [{}] is unauthorized for user [{}]", action, authUser.principal());
} }
static boolean isSuperuser(User user) { static boolean isSuperuser(User user) {

View File

@ -82,14 +82,14 @@ public final class AuthorizationUtils {
} }
public void authorize(AuthorizationService service) { public void authorize(AuthorizationService service) {
if (SystemUser.is(authentication.getUser())) { if (SystemUser.is(authentication.getUser().authenticatedUser())) {
assert authentication.getUser().isRunAs() == false;
setUserRoles(null); // we can inform the listener immediately - nothing to fetch for us on system user setUserRoles(null); // we can inform the listener immediately - nothing to fetch for us on system user
setRunAsRoles(null); setRunAsRoles(null);
} else { } else {
service.roles(authentication.getUser(), ActionListener.wrap(this::setUserRoles, listener::onFailure)); service.roles(authentication.getUser().authenticatedUser(), ActionListener.wrap(this::setUserRoles, listener::onFailure));
if (authentication.isRunAs()) { if (authentication.getUser().isRunAs()) {
assert authentication.getRunAsUser() != null : "runAs user is null but shouldn't"; service.roles(authentication.getUser(), ActionListener.wrap(this::setRunAsRoles, listener::onFailure));
service.roles(authentication.getRunAsUser(), ActionListener.wrap(this::setRunAsRoles, listener::onFailure));
} else { } else {
setRunAsRoles(null); setRunAsRoles(null);
} }

View File

@ -49,7 +49,7 @@ public class RestAuthenticateAction extends SecurityBaseRestHandler {
if (user == null) { if (user == null) {
return restChannel -> { throw new IllegalStateException("we should never have a null user and invoke this consumer"); }; return restChannel -> { throw new IllegalStateException("we should never have a null user and invoke this consumer"); };
} }
final String username = user.runAs() == null ? user.principal() : user.runAs().principal(); final String username = user.principal();
return channel -> client.execute(AuthenticateAction.INSTANCE, new AuthenticateRequest(username), return channel -> client.execute(AuthenticateAction.INSTANCE, new AuthenticateRequest(username),
new RestBuilderListener<AuthenticateResponse>(channel) { new RestBuilderListener<AuthenticateResponse>(channel) {

View File

@ -48,7 +48,7 @@ public class RestChangePasswordAction extends SecurityBaseRestHandler implements
final User user = securityContext.getUser(); final User user = securityContext.getUser();
final String username; final String username;
if (request.param("username") == null) { if (request.param("username") == null) {
username = user.runAs() == null ? user.principal() : user.runAs().principal(); username = user.principal();
} else { } else {
username = request.param("username"); username = request.param("username");
} }

View File

@ -59,12 +59,7 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
if (username != null) { if (username != null) {
return username; return username;
} }
final User user = securityContext.getUser(); return securityContext.getUser().principal();
if (user.runAs() != null) {
return user.runAs().principal();
} else {
return user.principal();
}
} }
static class HasPrivilegesRestResponseBuilder extends RestBuilderListener<HasPrivilegesResponse> { static class HasPrivilegesRestResponseBuilder extends RestBuilderListener<HasPrivilegesResponse> {

View File

@ -27,6 +27,7 @@ import org.elasticsearch.transport.TransportRequestOptions;
import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportResponseHandler; import org.elasticsearch.transport.TransportResponseHandler;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import org.elasticsearch.transport.TransportService.ContextRestoreResponseHandler;
import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.transport.TransportSettings;
import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.SecurityContext;
@ -91,7 +92,7 @@ public class SecurityServerTransportInterceptor extends AbstractComponent implem
// which means that the user is copied over to system actions so we need to change the user // which means that the user is copied over to system actions so we need to change the user
if (AuthorizationUtils.shouldReplaceUserWithSystem(threadPool.getThreadContext(), action)) { if (AuthorizationUtils.shouldReplaceUserWithSystem(threadPool.getThreadContext(), action)) {
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> sendWithUser(connection, action, request, options, securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> sendWithUser(connection, action, request, options,
new TransportService.ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original) new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original)
, handler), sender), connection.getVersion()); , handler), sender), connection.getVersion());
} else if (reservedRealmEnabled && connection.getVersion().before(Version.V_5_2_0_UNRELEASED) && } else if (reservedRealmEnabled && connection.getVersion().before(Version.V_5_2_0_UNRELEASED) &&
KibanaUser.NAME.equals(securityContext.getUser().principal())) { KibanaUser.NAME.equals(securityContext.getUser().principal())) {
@ -99,16 +100,14 @@ public class SecurityServerTransportInterceptor extends AbstractComponent implem
final User bwcKibanaUser = new User(kibanaUser.principal(), new String[] { "kibana" }, kibanaUser.fullName(), final User bwcKibanaUser = new User(kibanaUser.principal(), new String[] { "kibana" }, kibanaUser.fullName(),
kibanaUser.email(), kibanaUser.metadata(), kibanaUser.enabled()); kibanaUser.email(), kibanaUser.metadata(), kibanaUser.enabled());
securityContext.executeAsUser(bwcKibanaUser, (original) -> sendWithUser(connection, action, request, options, securityContext.executeAsUser(bwcKibanaUser, (original) -> sendWithUser(connection, action, request, options,
new TransportService.ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original), new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original),
handler), sender), connection.getVersion()); handler), sender), connection.getVersion());
} else if (securityContext.getAuthentication() != null && } else if (securityContext.getAuthentication() != null &&
securityContext.getAuthentication().getVersion().equals(connection.getVersion()) == false) { securityContext.getAuthentication().getVersion().equals(connection.getVersion()) == false) {
// re-write the authentication since we want the authentication version to match the version of the connection // re-write the authentication since we want the authentication version to match the version of the connection
securityContext.executeAsUser(securityContext.getUser(), securityContext.executeAfterRewritingAuthentication(original -> sendWithUser(connection, action, request, options,
(original) -> sendWithUser(connection, action, request, options, new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original), handler), sender),
new TransportService.ContextRestoreResponseHandler<>( connection.getVersion());
threadPool.getThreadContext().wrapRestorable(original), handler), sender),
connection.getVersion());
} else { } else {
sendWithUser(connection, action, request, options, handler, sender); sendWithUser(connection, action, request, options, handler, sender);
} }

View File

@ -124,25 +124,11 @@ public interface ServerTransportFilter {
} }
authcService.authenticate(securityAction, request, null, ActionListener.wrap((authentication) -> { authcService.authenticate(securityAction, request, null, ActionListener.wrap((authentication) -> {
if (reservedRealmEnabled && authentication.getVersion().before(Version.V_5_2_0_UNRELEASED) if (reservedRealmEnabled && authentication.getVersion().before(Version.V_5_2_0_UNRELEASED) &&
&& KibanaUser.NAME.equals(authentication.getUser().principal())) { KibanaUser.NAME.equals(authentication.getUser().authenticatedUser().principal())) {
// the authentication came from an older node - so let's replace the user with our version executeAsCurrentVersionKibanaUser(securityAction, request, transportChannel, listener, authentication);
final User kibanaUser = new KibanaUser(authentication.getUser().enabled());
if (kibanaUser.enabled()) {
securityContext.executeAsUser(kibanaUser, (original) -> {
final Authentication replacedUserAuth = Authentication.getAuthentication(threadContext);
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer =
new AuthorizationUtils.AsyncAuthorizer(replacedUserAuth, listener, (userRoles, runAsRoles) -> {
authzService.authorize(replacedUserAuth, securityAction, request, userRoles, runAsRoles);
listener.onResponse(null);
});
asyncAuthorizer.authorize(authzService);
}, transportChannel.getVersion());
} else {
throw new IllegalStateException("a disabled user should never be sent. " + kibanaUser);
}
} else if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) && } else if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) &&
SystemUser.is(authentication.getUser()) == false) { SystemUser.is(authentication.getUser()) == false) {
securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> { securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> {
final Authentication replaced = Authentication.getAuthentication(threadContext); final Authentication replaced = Authentication.getAuthentication(threadContext);
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer = final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer =
@ -162,6 +148,25 @@ public interface ServerTransportFilter {
} }
}, listener::onFailure)); }, listener::onFailure));
} }
private void executeAsCurrentVersionKibanaUser(String securityAction, TransportRequest request, TransportChannel transportChannel,
ActionListener<Void> listener, Authentication authentication) {
// the authentication came from an older node - so let's replace the user with our version
final User kibanaUser = new KibanaUser(authentication.getUser().enabled());
if (kibanaUser.enabled()) {
securityContext.executeAsUser(kibanaUser, (original) -> {
final Authentication replacedUserAuth = securityContext.getAuthentication();
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer =
new AuthorizationUtils.AsyncAuthorizer(replacedUserAuth, listener, (userRoles, runAsRoles) -> {
authzService.authorize(replacedUserAuth, securityAction, request, userRoles, runAsRoles);
listener.onResponse(null);
});
asyncAuthorizer.authorize(authzService);
}, transportChannel.getVersion());
} else {
throw new IllegalStateException("a disabled user should never be sent. " + kibanaUser);
}
}
} }
static void extactClientCertificates(Logger logger, ThreadContext threadContext, SSLEngine sslEngine, Object channel) { static void extactClientCertificates(Logger logger, ThreadContext threadContext, SSLEngine sslEngine, Object channel) {

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.security.user; package org.elasticsearch.xpack.security.user;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.inject.internal.Nullable;
@ -27,7 +28,7 @@ public class User implements ToXContentObject {
private final String username; private final String username;
private final String[] roles; private final String[] roles;
private final User runAs; private final User authenticatedUser;
private final Map<String, Object> metadata; private final Map<String, Object> metadata;
private final boolean enabled; private final boolean enabled;
@ -38,36 +39,28 @@ public class User implements ToXContentObject {
this(username, roles, null, null, null, true); this(username, roles, null, null, null, true);
} }
public User(String username, String[] roles, User runAs) { public User(String username, String[] roles, User authenticatedUser) {
this(username, roles, null, null, null, true, runAs); this(username, roles, null, null, null, true, authenticatedUser);
} }
public User(User user, User runAs) { public User(User user, User authenticatedUser) {
this(user.principal(), user.roles(), user.fullName(), user.email(), user.metadata(), user.enabled(), runAs); this(user.principal(), user.roles(), user.fullName(), user.email(), user.metadata(), user.enabled(), authenticatedUser);
} }
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled) { public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled) {
this.username = username; this(username, roles, fullName, email, metadata, enabled, null);
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
this.fullName = fullName;
this.email = email;
this.enabled = enabled;
this.runAs = null;
} }
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled, User runAs) { private User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled,
User authenticatedUser) {
this.username = username; this.username = username;
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles; this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap(); this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
this.fullName = fullName; this.fullName = fullName;
this.email = email; this.email = email;
this.enabled = enabled; this.enabled = enabled;
assert (runAs == null || runAs.runAs() == null) : "the run_as user should not be a user that can run as"; assert (authenticatedUser == null || authenticatedUser.isRunAs() == false) : "the authenticated user should not be a run_as user";
if (runAs == SystemUser.INSTANCE) { this.authenticatedUser = authenticatedUser;
throw new ElasticsearchSecurityException("invalid run_as user");
}
this.runAs = runAs;
} }
/** /**
@ -116,12 +109,16 @@ public class User implements ToXContentObject {
} }
/** /**
* @return The user that will be used for run as functionality. If run as * @return The user that was originally authenticated.
* functionality is not being used, then <code>null</code> will be * This may be the user itself, or a different user which used runAs.
* returned
*/ */
public User runAs() { public User authenticatedUser() {
return runAs; return authenticatedUser == null ? this : authenticatedUser;
}
/** Return true if this user was not the originally authenticated user, false otherwise. */
public boolean isRunAs() {
return authenticatedUser != null;
} }
@Override @Override
@ -133,8 +130,8 @@ public class User implements ToXContentObject {
sb.append(",email=").append(email); sb.append(",email=").append(email);
sb.append(",metadata="); sb.append(",metadata=");
MetadataUtils.writeValue(sb, metadata); MetadataUtils.writeValue(sb, metadata);
if (runAs != null) { if (authenticatedUser != null) {
sb.append(",runAs=[").append(runAs.toString()).append("]"); sb.append(",authenticatedUser=[").append(authenticatedUser.toString()).append("]");
} }
sb.append("]"); sb.append("]");
return sb.toString(); return sb.toString();
@ -150,7 +147,7 @@ public class User implements ToXContentObject {
if (!username.equals(user.username)) return false; if (!username.equals(user.username)) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals // Probably incorrect - comparing Object[] arrays with Arrays.equals
if (!Arrays.equals(roles, user.roles)) return false; if (!Arrays.equals(roles, user.roles)) return false;
if (runAs != null ? !runAs.equals(user.runAs) : user.runAs != null) return false; if (authenticatedUser != null ? !authenticatedUser.equals(user.authenticatedUser) : user.authenticatedUser != null) return false;
if (!metadata.equals(user.metadata)) return false; if (!metadata.equals(user.metadata)) return false;
if (fullName != null ? !fullName.equals(user.fullName) : user.fullName != null) return false; if (fullName != null ? !fullName.equals(user.fullName) : user.fullName != null) return false;
return !(email != null ? !email.equals(user.email) : user.email != null); return !(email != null ? !email.equals(user.email) : user.email != null);
@ -161,7 +158,7 @@ public class User implements ToXContentObject {
public int hashCode() { public int hashCode() {
int result = username.hashCode(); int result = username.hashCode();
result = 31 * result + Arrays.hashCode(roles); result = 31 * result + Arrays.hashCode(roles);
result = 31 * result + (runAs != null ? runAs.hashCode() : 0); result = 31 * result + (authenticatedUser != null ? authenticatedUser.hashCode() : 0);
result = 31 * result + metadata.hashCode(); result = 31 * result + metadata.hashCode();
result = 31 * result + (fullName != null ? fullName.hashCode() : 0); result = 31 * result + (fullName != null ? fullName.hashCode() : 0);
result = 31 * result + (email != null ? email.hashCode() : 0); result = 31 * result + (email != null ? email.hashCode() : 0);
@ -196,8 +193,19 @@ public class User implements ToXContentObject {
String fullName = input.readOptionalString(); String fullName = input.readOptionalString();
String email = input.readOptionalString(); String email = input.readOptionalString();
boolean enabled = input.readBoolean(); boolean enabled = input.readBoolean();
User runAs = input.readBoolean() ? readFrom(input) : null; User outerUser = new User(username, roles, fullName, email, metadata, enabled, null);
return new User(username, roles, fullName, email, metadata, enabled, runAs); boolean hasInnerUser = input.readBoolean();
if (hasInnerUser) {
User innerUser = readFrom(input);
if (input.getVersion().onOrBefore(Version.V_5_4_0_UNRELEASED)) {
// backcompat: runas user was read first, so reverse outer and inner
return new User(innerUser, outerUser);
} else {
return new User(outerUser, innerUser);
}
} else {
return outerUser;
}
} }
public static void writeTo(User user, StreamOutput output) throws IOException { public static void writeTo(User user, StreamOutput output) throws IOException {
@ -208,22 +216,34 @@ public class User implements ToXContentObject {
output.writeBoolean(true); output.writeBoolean(true);
output.writeString(XPackUser.NAME); output.writeString(XPackUser.NAME);
} else { } else {
output.writeBoolean(false); if (user.authenticatedUser == null) {
output.writeString(user.username); // no backcompat necessary, since there is no inner user
output.writeStringArray(user.roles); writeUser(user, output);
output.writeMap(user.metadata); } else if (output.getVersion().onOrBefore(Version.V_5_4_0_UNRELEASED)) {
output.writeOptionalString(user.fullName); // backcompat: write runas user as the "inner" user
output.writeOptionalString(user.email); writeUser(user.authenticatedUser, output);
output.writeBoolean(user.enabled);
if (user.runAs == null) {
output.writeBoolean(false);
} else {
output.writeBoolean(true); output.writeBoolean(true);
writeTo(user.runAs, output); writeUser(user, output);
} else {
writeUser(user, output);
output.writeBoolean(true);
writeUser(user.authenticatedUser, output);
} }
output.writeBoolean(false); // last user written, regardless of bwc, does not have an inner user
} }
} }
/** 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
output.writeString(user.username);
output.writeStringArray(user.roles);
output.writeMap(user.metadata);
output.writeOptionalString(user.fullName);
output.writeOptionalString(user.email);
output.writeBoolean(user.enabled);
}
public interface Fields { public interface Fields {
ParseField USERNAME = new ParseField("username"); ParseField USERNAME = new ParseField("username");
ParseField PASSWORD = new ParseField("password"); ParseField PASSWORD = new ParseField("password");

View File

@ -63,7 +63,7 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
when(authentication.getRunAsUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
AuthorizationService authorizationService = mock(AuthorizationService.class); AuthorizationService authorizationService = mock(AuthorizationService.class);
Mockito.doAnswer(invocationOnMock -> { Mockito.doAnswer(invocationOnMock -> {

View File

@ -56,7 +56,7 @@ public class TransportSetEnabledActionTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(authentication.getRunAsUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
NativeUsersStore usersStore = mock(NativeUsersStore.class); NativeUsersStore usersStore = mock(NativeUsersStore.class);
TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
x -> null, null); x -> null, null);
@ -94,7 +94,7 @@ public class TransportSetEnabledActionTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(authentication.getRunAsUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
NativeUsersStore usersStore = mock(NativeUsersStore.class); NativeUsersStore usersStore = mock(NativeUsersStore.class);
TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
x -> null, null); x -> null, null);
@ -131,7 +131,7 @@ public class TransportSetEnabledActionTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(authentication.getRunAsUser()).thenReturn(new User("the runner")); when(authentication.getUser()).thenReturn(new User("the runner"));
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
NativeUsersStore usersStore = mock(NativeUsersStore.class); NativeUsersStore usersStore = mock(NativeUsersStore.class);
@ -182,7 +182,7 @@ public class TransportSetEnabledActionTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(authentication.getRunAsUser()).thenReturn(new User("the runner")); when(authentication.getUser()).thenReturn(new User("the runner"));
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
NativeUsersStore usersStore = mock(NativeUsersStore.class); NativeUsersStore usersStore = mock(NativeUsersStore.class);
@ -235,7 +235,7 @@ public class TransportSetEnabledActionTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(authentication.getRunAsUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
NativeUsersStore usersStore = mock(NativeUsersStore.class); NativeUsersStore usersStore = mock(NativeUsersStore.class);
SetEnabledRequest request = new SetEnabledRequest(); SetEnabledRequest request = new SetEnabledRequest();

View File

@ -0,0 +1,185 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.audit.index;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.message.BasicHeader;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.recovery.RecoveryAction;
import org.elasticsearch.action.admin.indices.recovery.RecoveryRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.audit.AuditTrail;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import static org.elasticsearch.test.SecuritySettingsSource.DEFAULT_PASSWORD_SECURE_STRING;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.iterableWithSize;
public class AuditTrailTests extends SecurityIntegTestCase {
private static final String AUTHENTICATE_USER = "http_user";
private static final String EXECUTE_USER = "exec_user";
private static final String ROLE_CAN_RUN_AS = "can_run_as";
private static final String ROLES = ROLE_CAN_RUN_AS + ":\n" + " run_as: [ '" + EXECUTE_USER + "' ]\n";
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.HTTP_ENABLED.getKey(), true)
.put("xpack.security.audit.enabled", true)
.put("xpack.security.audit.outputs", "index")
.putArray("xpack.security.audit.index.events.include", "access_denied", "authentication_failed", "run_as_denied")
.build();
}
@Override
public String configRoles() {
return ROLES + super.configRoles();
}
@Override
public String configUsers() {
return super.configUsers()
+ AUTHENTICATE_USER + ":" + SecuritySettingsSource.DEFAULT_PASSWORD_HASHED + "\n"
+ EXECUTE_USER + ":xx_no_password_xx\n";
}
@Override
public String configUsersRoles() {
return super.configUsersRoles()
+ ROLE_CAN_RUN_AS + ":" + AUTHENTICATE_USER + "\n"
+ "kibana_user:" + EXECUTE_USER;
}
@Override
public boolean useGeneratedSSLConfig() {
return true;
}
public void testAuditAccessDeniedWithRunAsUser() throws Exception {
try {
getRestClient().performRequest("GET", "/.security/_search",
new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
UsernamePasswordToken.basicAuthHeaderValue(AUTHENTICATE_USER, DEFAULT_PASSWORD_SECURE_STRING)),
new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, EXECUTE_USER));
fail("request should have failed");
} catch (ResponseException e) {
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(403));
}
final Collection<Map<String, Object>> events = waitForAuditEvents();
assertThat(events, iterableWithSize(1));
final Map<String, Object> event = events.iterator().next();
assertThat(event.get(IndexAuditTrail.Field.TYPE), equalTo("access_denied"));
assertThat((List<?>) event.get(IndexAuditTrail.Field.INDICES), containsInAnyOrder(".security"));
assertThat(event.get(IndexAuditTrail.Field.PRINCIPAL), equalTo(EXECUTE_USER));
assertThat(event.get(IndexAuditTrail.Field.RUN_BY_PRINCIPAL), equalTo(AUTHENTICATE_USER));
}
public void testAuditRunAsDeniedEmptyUser() throws Exception {
try {
getRestClient().performRequest("GET", "/.security/_search",
new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
UsernamePasswordToken.basicAuthHeaderValue(AUTHENTICATE_USER, DEFAULT_PASSWORD_SECURE_STRING)),
new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, ""));
fail("request should have failed");
} catch (ResponseException e) {
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401));
}
final Collection<Map<String, Object>> events = waitForAuditEvents();
assertThat(events, iterableWithSize(1));
final Map<String, Object> event = events.iterator().next();
assertThat(event.get(IndexAuditTrail.Field.TYPE), equalTo("run_as_denied"));
assertThat(event.get(IndexAuditTrail.Field.PRINCIPAL), equalTo(""));
assertThat(event.get(IndexAuditTrail.Field.RUN_BY_PRINCIPAL), equalTo(AUTHENTICATE_USER));
}
private Collection<Map<String, Object>> waitForAuditEvents() throws InterruptedException {
waitForAuditTrailToBeWritten();
AtomicReference<Collection<Map<String, Object>>> eventsRef = new AtomicReference<>();
awaitBusy(() -> {
try {
final Collection<Map<String, Object>> events = getAuditEvents();
eventsRef.set(events);
return events.size() > 0;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return eventsRef.get();
}
private Collection<Map<String, Object>> getAuditEvents() throws Exception {
final InternalClient client = internalClient();
DateTime now = new DateTime(DateTimeZone.UTC);
String indexName = IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, now, IndexNameResolver.Rollover.DAILY);
assertTrue(awaitBusy(() -> indexExists(client, indexName), 5, TimeUnit.SECONDS));
client.admin().indices().refresh(Requests.refreshRequest(indexName)).get();
SearchRequest request = client.prepareSearch(indexName)
.setTypes(IndexAuditTrail.DOC_TYPE)
.setQuery(QueryBuilders.matchAllQuery())
.setSize(1000)
.setFetchSource(true)
.request();
request.indicesOptions().ignoreUnavailable();
PlainActionFuture<Collection<Map<String, Object>>> listener = new PlainActionFuture();
InternalClient.fetchAllByEntity(client, request, listener, SearchHit::getSourceAsMap);
return listener.get();
}
private boolean indexExists(Client client, String indexName) {
try {
final ActionFuture<IndicesExistsResponse> future = client.admin().indices().exists(Requests.indicesExistsRequest(indexName));
return future.get().isExists();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to check if " + indexName + " exists", e);
}
}
private void waitForAuditTrailToBeWritten() throws InterruptedException {
final AuditTrailService auditTrailService = (AuditTrailService) internalCluster().getInstance(AuditTrail.class);
assertThat(auditTrailService.getAuditTrails(), iterableWithSize(1));
final IndexAuditTrail indexAuditTrail = (IndexAuditTrail) auditTrailService.getAuditTrails().get(0);
assertTrue(awaitBusy(() -> indexAuditTrail.peek() == null, 5, TimeUnit.SECONDS));
}
}

View File

@ -473,8 +473,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -518,8 +517,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -578,8 +576,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -631,7 +628,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
public void testRunAsGranted() throws Exception { public void testRunAsGranted() throws Exception {
initialize(); initialize();
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage()); TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
auditor.runAsGranted(user, "_action", message); auditor.runAsGranted(user, "_action", message);
SearchHit hit = getIndexedAuditMessage(enqueuedMessage.get()); SearchHit hit = getIndexedAuditMessage(enqueuedMessage.get());
@ -647,7 +644,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
public void testRunAsDenied() throws Exception { public void testRunAsDenied() throws Exception {
initialize(); initialize();
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage()); TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
auditor.runAsDenied(user, "_action", message); auditor.runAsDenied(user, "_action", message);
SearchHit hit = getIndexedAuditMessage(enqueuedMessage.get()); SearchHit hit = getIndexedAuditMessage(enqueuedMessage.get());
@ -666,7 +663,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[] { "r1" }, new User("running as", new String[] { "r2" })); user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
} else { } else {
user = new User("_username", new String[] { "r1" }); user = new User("_username", new String[] { "r1" });
} }
@ -693,7 +690,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[] { "r1" }, new User("running as", new String[] { "r2" })); user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
} else { } else {
user = new User("_username", new String[] { "r1" }); user = new User("_username", new String[] { "r1" });
} }

View File

@ -326,8 +326,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
boolean runAs = randomBoolean(); boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -379,8 +378,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
boolean runAs = randomBoolean(); boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -410,8 +408,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
boolean runAs = randomBoolean(); boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
new User("running as", new String[] {"r2"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -481,7 +478,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
final boolean runAs = randomBoolean(); final boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
} else { } else {
user = new User("_username", new String[]{"r1"}); user = new User("_username", new String[]{"r1"});
} }
@ -547,7 +544,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext);
TransportMessage message = new MockMessage(threadContext); TransportMessage message = new MockMessage(threadContext);
String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext);
User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
auditTrail.runAsGranted(user, "_action", message); auditTrail.runAsGranted(user, "_action", message);
assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_granted]\t" + origins + assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_granted]\t" + origins +
", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]"); ", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]");
@ -565,7 +562,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext);
TransportMessage message = new MockMessage(threadContext); TransportMessage message = new MockMessage(threadContext);
String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext);
User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
auditTrail.runAsDenied(user, "_action", message); auditTrail.runAsDenied(user, "_action", message);
assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_denied]\t" + origins + assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_denied]\t" + origins +
", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]"); ", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]");
@ -608,7 +605,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
boolean runAs = randomBoolean(); boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[] { "r1" }, new User("running as", new String[] { "r2" })); user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
} else { } else {
user = new User("_username", new String[] { "r1" }); user = new User("_username", new String[] { "r1" });
} }
@ -649,7 +646,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
boolean runAs = randomBoolean(); boolean runAs = randomBoolean();
User user; User user;
if (runAs) { if (runAs) {
user = new User("_username", new String[] { "r1" }, new User("running as", new String[] { "r2" })); user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"}));
} else { } else {
user = new User("_username", new String[] { "r1" }); user = new User("_username", new String[] { "r1" });
} }

View File

@ -645,18 +645,21 @@ public class AuthenticationServiceTests extends ESTestCase {
assertThat(result, notNullValue()); assertThat(result, notNullValue());
User authenticated = result.getUser(); User authenticated = result.getUser();
assertThat(SystemUser.is(authenticated), is(false)); assertThat(authenticated.principal(), is("looked up user"));
assertThat(authenticated.runAs(), is(notNullValue())); assertThat(authenticated.roles(), arrayContaining("some role"));
assertThat(authenticated.principal(), is("lookup user"));
assertThat(authenticated.roles(), arrayContaining("user"));
assertEquals(user.metadata(), authenticated.metadata());
assertEquals(user.email(), authenticated.email());
assertEquals(user.enabled(), authenticated.enabled());
assertEquals(user.fullName(), authenticated.fullName());
assertThat(authenticated.runAs().principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
assertThreadContextContainsAuthentication(result); assertThreadContextContainsAuthentication(result);
assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.isRunAs(), is(true));
User authUser = authenticated.authenticatedUser();
assertThat(authUser.principal(), is("lookup user"));
assertThat(authUser.roles(), arrayContaining("user"));
assertEquals(user.metadata(), authUser.metadata());
assertEquals(user.email(), authUser.email());
assertEquals(user.enabled(), authUser.enabled());
assertEquals(user.fullName(), authUser.fullName());
setCompletedToTrue(completed); setCompletedToTrue(completed);
}, this::logAndFail); }, this::logAndFail);
@ -687,11 +690,11 @@ public class AuthenticationServiceTests extends ESTestCase {
User authenticated = result.getUser(); User authenticated = result.getUser();
assertThat(SystemUser.is(authenticated), is(false)); assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.runAs(), is(notNullValue())); assertThat(authenticated.isRunAs(), is(true));
assertThat(authenticated.principal(), is("lookup user")); assertThat(authenticated.authenticatedUser().principal(), is("lookup user"));
assertThat(authenticated.roles(), arrayContaining("user")); assertThat(authenticated.authenticatedUser().roles(), arrayContaining("user"));
assertThat(authenticated.runAs().principal(), is("looked up user")); assertThat(authenticated.principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role")); assertThat(authenticated.roles(), arrayContaining("some role"));
assertThreadContextContainsAuthentication(result); assertThreadContextContainsAuthentication(result);
setCompletedToTrue(completed); setCompletedToTrue(completed);
}, this::logAndFail); }, this::logAndFail);

View File

@ -456,8 +456,8 @@ public class AuthorizationServiceTests extends ESTestCase {
public void testRunAsRequestWithNoRolesUser() { public void testRunAsRequestWithNoRolesUser() {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
User user = new User("test user", null, new User("run as me", new String[] { "admin" })); User user = new User("run as me", null, new User("test user", "admin"));
assertThat(user.runAs(), is(notNullValue())); assertNotEquals(user.authenticatedUser(), user);
assertThrowsAuthorizationExceptionRunAs( assertThrowsAuthorizationExceptionRunAs(
() -> authorize(createAuthentication(user), "indices:a", request), () -> authorize(createAuthentication(user), "indices:a", request),
"indices:a", "test user", "run as me"); // run as [run as me] "indices:a", "test user", "run as me"); // run as [run as me]
@ -468,9 +468,9 @@ public class AuthorizationServiceTests extends ESTestCase {
public void testRunAsRequestWithoutLookedUpBy() { public void testRunAsRequestWithoutLookedUpBy() {
AuthenticateRequest request = new AuthenticateRequest("run as me"); AuthenticateRequest request = new AuthenticateRequest("run as me");
roleMap.put("can run as", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); roleMap.put("can run as", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR);
User user = new User("test user", new String[] { "can run as" }, new User("run as me", Strings.EMPTY_ARRAY)); User user = new User("run as me", Strings.EMPTY_ARRAY, new User("test user", new String[] { "can run as" }));
Authentication authentication = new Authentication(user, new RealmRef("foo", "bar", "baz"), null); Authentication authentication = new Authentication(user, new RealmRef("foo", "bar", "baz"), null);
assertThat(user.runAs(), is(notNullValue())); assertNotEquals(user.authenticatedUser(), user);
assertThrowsAuthorizationExceptionRunAs( assertThrowsAuthorizationExceptionRunAs(
() -> authorize(authentication, AuthenticateAction.NAME, request), () -> authorize(authentication, AuthenticateAction.NAME, request),
AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me] AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me]
@ -480,8 +480,8 @@ public class AuthorizationServiceTests extends ESTestCase {
public void testRunAsRequestRunningAsUnAllowedUser() { public void testRunAsRequestRunningAsUnAllowedUser() {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
User user = new User("test user", new String[] { "can run as" }, new User("run as me", "doesn't exist")); User user = new User("run as me", new String[] {"doesn't exist"}, new User("test user", "can run as"));
assertThat(user.runAs(), is(notNullValue())); assertNotEquals(user.authenticatedUser(), user);
roleMap.put("can run as", new RoleDescriptor("can run as",null, roleMap.put("can run as", new RoleDescriptor("can run as",null,
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },
new String[] { "not the right user" })); new String[] { "not the right user" }));
@ -495,8 +495,8 @@ public class AuthorizationServiceTests extends ESTestCase {
public void testRunAsRequestWithRunAsUserWithoutPermission() { public void testRunAsRequestWithRunAsUserWithoutPermission() {
TransportRequest request = new GetIndexRequest().indices("a"); TransportRequest request = new GetIndexRequest().indices("a");
User user = new User("test user", new String[] { "can run as" }, new User("run as me", "b")); User user = new User("run as me", new String[] {"b"}, new User("test user", "can run as"));
assertThat(user.runAs(), is(notNullValue())); assertNotEquals(user.authenticatedUser(), user);
roleMap.put("can run as", new RoleDescriptor("can run as",null, roleMap.put("can run as", new RoleDescriptor("can run as",null,
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },
new String[] { "run as me" })); new String[] { "run as me" }));
@ -525,8 +525,8 @@ public class AuthorizationServiceTests extends ESTestCase {
public void testRunAsRequestWithValidPermissions() { public void testRunAsRequestWithValidPermissions() {
TransportRequest request = new GetIndexRequest().indices("b"); TransportRequest request = new GetIndexRequest().indices("b");
User user = new User("test user", new String[] { "can run as" }, new User("run as me", "b")); User user = new User("run as me", new String[] {"b"}, new User("test user", new String[] { "can run as" }));
assertThat(user.runAs(), is(notNullValue())); assertNotEquals(user.authenticatedUser(), user);
roleMap.put("can run as", new RoleDescriptor("can run as",null, roleMap.put("can run as", new RoleDescriptor("can run as",null,
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },
new String[] { "run as me" })); new String[] { "run as me" }));
@ -845,7 +845,6 @@ public class AuthorizationServiceTests extends ESTestCase {
final Authentication authentication = mock(Authentication.class); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()) when(authenticatedBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12)); .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12));
@ -855,7 +854,8 @@ public class AuthorizationServiceTests extends ESTestCase {
} }
public void testSameUserPermissionDoesNotAllowNonMatchingUsername() { public void testSameUserPermissionDoesNotAllowNonMatchingUsername() {
final User user = new User("joe"); final User authUser = new User("admin", new String[] { "bar" });
final User user = new User("joe", null, authUser);
final boolean changePasswordRequest = randomBoolean(); final boolean changePasswordRequest = randomBoolean();
final String username = randomFrom("", "joe" + randomAlphaOfLengthBetween(1, 5), randomAlphaOfLengthBetween(3, 10)); final String username = randomFrom("", "joe" + randomAlphaOfLengthBetween(1, 5), randomAlphaOfLengthBetween(3, 10));
final TransportRequest request = changePasswordRequest ? final TransportRequest request = changePasswordRequest ?
@ -865,7 +865,6 @@ public class AuthorizationServiceTests extends ESTestCase {
final Authentication authentication = mock(Authentication.class); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()) when(authenticatedBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12)); .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12));
@ -873,9 +872,7 @@ public class AuthorizationServiceTests extends ESTestCase {
assertThat(request, instanceOf(UserRequest.class)); assertThat(request, instanceOf(UserRequest.class));
assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication));
final User user2 = new User("admin", new String[] { "bar" }, user); when(authentication.getUser()).thenReturn(user);
when(authentication.getUser()).thenReturn(user2);
when(authentication.getRunAsUser()).thenReturn(user);
final RealmRef lookedUpBy = mock(RealmRef.class); final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(lookedUpBy.getType()) when(lookedUpBy.getType())
@ -898,8 +895,10 @@ public class AuthorizationServiceTests extends ESTestCase {
ClusterStatsAction.NAME, GetLicenseAction.NAME); ClusterStatsAction.NAME, GetLicenseAction.NAME);
final Authentication authentication = mock(Authentication.class); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
final boolean runAs = randomBoolean();
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(randomBoolean() ? user : new User("runAs")); when(user.authenticatedUser()).thenReturn(runAs ? new User("authUser") : user);
when(user.isRunAs()).thenReturn(runAs);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()) when(authenticatedBy.getType())
.thenReturn(randomAlphaOfLengthBetween(4, 12)); .thenReturn(randomAlphaOfLengthBetween(4, 12));
@ -909,9 +908,9 @@ public class AuthorizationServiceTests extends ESTestCase {
} }
public void testSameUserPermissionRunAsChecksAuthenticatedBy() { public void testSameUserPermissionRunAsChecksAuthenticatedBy() {
final User authUser = new User("admin", new String[] { "bar" });
final String username = "joe"; final String username = "joe";
final User runAs = new User(username); final User user = new User(username, null, authUser);
final User user = new User("admin", new String[] { "bar" }, runAs);
final boolean changePasswordRequest = randomBoolean(); final boolean changePasswordRequest = randomBoolean();
final TransportRequest request = changePasswordRequest ? final TransportRequest request = changePasswordRequest ?
new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() :
@ -921,15 +920,13 @@ public class AuthorizationServiceTests extends ESTestCase {
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
final RealmRef lookedUpBy = mock(RealmRef.class); final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(runAs);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(authentication.isRunAs()).thenReturn(true);
when(lookedUpBy.getType()) when(lookedUpBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12)); .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAlphaOfLengthBetween(4, 12));
assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication)); assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication));
when(authentication.getRunAsUser()).thenReturn(user); when(authentication.getUser()).thenReturn(authUser);
assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication));
} }
@ -940,8 +937,6 @@ public class AuthorizationServiceTests extends ESTestCase {
final Authentication authentication = mock(Authentication.class); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.isRunAs()).thenReturn(false);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, when(authenticatedBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE,
randomAlphaOfLengthBetween(4, 12))); randomAlphaOfLengthBetween(4, 12)));
@ -949,23 +944,20 @@ public class AuthorizationServiceTests extends ESTestCase {
assertThat(request, instanceOf(UserRequest.class)); assertThat(request, instanceOf(UserRequest.class));
assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication));
verify(authenticatedBy).getType(); verify(authenticatedBy).getType();
verify(authentication).getRunAsUser();
verify(authentication).getAuthenticatedBy(); verify(authentication).getAuthenticatedBy();
verify(authentication).isRunAs(); verify(authentication, times(2)).getUser();
verifyNoMoreInteractions(authenticatedBy, authentication); verifyNoMoreInteractions(authenticatedBy, authentication);
} }
public void testSameUserPermissionDoesNotAllowChangePasswordForLookedUpByOtherRealms() { public void testSameUserPermissionDoesNotAllowChangePasswordForLookedUpByOtherRealms() {
final User runAs = new User("joe"); final User authUser = new User("admin", new String[] { "bar" });
final User user = new User("admin", new String[] { "bar" }, runAs); final User user = new User("joe", null, authUser);
final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(runAs.principal()).request(); final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request();
final String action = ChangePasswordAction.NAME; final String action = ChangePasswordAction.NAME;
final Authentication authentication = mock(Authentication.class); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class); final RealmRef authenticatedBy = mock(RealmRef.class);
final RealmRef lookedUpBy = mock(RealmRef.class); final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(runAs);
when(authentication.isRunAs()).thenReturn(true);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(lookedUpBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, when(lookedUpBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE,
@ -974,8 +966,7 @@ public class AuthorizationServiceTests extends ESTestCase {
assertThat(request, instanceOf(UserRequest.class)); assertThat(request, instanceOf(UserRequest.class));
assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication));
verify(authentication).getLookedUpBy(); verify(authentication).getLookedUpBy();
verify(authentication).getRunAsUser(); verify(authentication, times(2)).getUser();
verify(authentication).isRunAs();
verify(lookedUpBy).getType(); verify(lookedUpBy).getType();
verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy); verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy);
} }
@ -1021,7 +1012,7 @@ public class AuthorizationServiceTests extends ESTestCase {
} }
private static Authentication createAuthentication(User user) { private static Authentication createAuthentication(User user) {
RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by"); RealmRef lookedUpBy = user.authenticatedUser() == user ? null : new RealmRef("looked", "up", "by");
return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy); return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy);
} }

View File

@ -81,7 +81,6 @@ public class ServerTransportFilterTests extends ESTestCase {
Authentication authentication = mock(Authentication.class); Authentication authentication = mock(Authentication.class);
when(authentication.getVersion()).thenReturn(Version.CURRENT); when(authentication.getVersion()).thenReturn(Version.CURRENT);
when(authentication.getUser()).thenReturn(SystemUser.INSTANCE); when(authentication.getUser()).thenReturn(SystemUser.INSTANCE);
when(authentication.getRunAsUser()).thenReturn(SystemUser.INSTANCE);
doAnswer((i) -> { doAnswer((i) -> {
ActionListener callback = ActionListener callback =
(ActionListener) i.getArguments()[3]; (ActionListener) i.getArguments()[3];
@ -160,7 +159,6 @@ public class ServerTransportFilterTests extends ESTestCase {
}).when(authzService).roles(any(User.class), any(ActionListener.class)); }).when(authzService).roles(any(User.class), any(ActionListener.class));
when(authentication.getVersion()).thenReturn(Version.CURRENT); when(authentication.getVersion()).thenReturn(Version.CURRENT);
when(authentication.getUser()).thenReturn(XPackUser.INSTANCE); when(authentication.getUser()).thenReturn(XPackUser.INSTANCE);
when(authentication.getRunAsUser()).thenReturn(XPackUser.INSTANCE);
PlainActionFuture<Void> future = new PlainActionFuture<>(); PlainActionFuture<Void> future = new PlainActionFuture<>();
doThrow(authorizationError("authz failed")).when(authzService).authorize(authentication, "_action", request, doThrow(authorizationError("authz failed")).when(authzService).authorize(authentication, "_action", request,
empty, null); empty, null);
@ -228,7 +226,6 @@ public class ServerTransportFilterTests extends ESTestCase {
final Version version = Version.fromId(randomIntBetween(Version.V_5_0_0_ID, Version.V_5_2_0_ID_UNRELEASED - 100)); final Version version = Version.fromId(randomIntBetween(Version.V_5_0_0_ID, Version.V_5_2_0_ID_UNRELEASED - 100));
when(authentication.getVersion()).thenReturn(version); when(authentication.getVersion()).thenReturn(version);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
doAnswer((i) -> { doAnswer((i) -> {
ActionListener callback = ActionListener callback =
(ActionListener) i.getArguments()[3]; (ActionListener) i.getArguments()[3];
@ -256,7 +253,6 @@ public class ServerTransportFilterTests extends ESTestCase {
rolesRef.set(null); rolesRef.set(null);
user = new KibanaUser(true); user = new KibanaUser(true);
when(authentication.getUser()).thenReturn(user); when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getVersion()).thenReturn(Version.V_5_2_0_UNRELEASED); when(authentication.getVersion()).thenReturn(Version.V_5_2_0_UNRELEASED);
future = new PlainActionFuture<>(); future = new PlainActionFuture<>();
filter.inbound("_action", request, channel, future); filter.inbound("_action", request, channel, future);

View File

@ -5,14 +5,15 @@
*/ */
package org.elasticsearch.xpack.security.user; package org.elasticsearch.xpack.security.user;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import static org.hamcrest.Matchers.containsString; import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@ -32,14 +33,15 @@ public class UserTests extends ESTestCase {
assertThat(readFrom, not(sameInstance(user))); assertThat(readFrom, not(sameInstance(user)));
assertThat(readFrom.principal(), is(user.principal())); assertThat(readFrom.principal(), is(user.principal()));
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true)); assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
assertThat(readFrom.runAs(), is(nullValue())); assertThat(readFrom.authenticatedUser(), is(user));
} }
public void testWriteToAndReadFromWithRunAs() throws Exception { public void testWriteToAndReadFromWithRunAs() throws Exception {
User runAs = new User(randomAlphaOfLengthBetween(4, 30), User authUser = new User(randomAlphaOfLengthBetween(4, 30), generateRandomStringArray(20, 30, false));
randomBoolean() ? generateRandomStringArray(20, 30, false) : null);
User user = new User(randomAlphaOfLengthBetween(4, 30), User user = new User(randomAlphaOfLengthBetween(4, 30),
generateRandomStringArray(20, 30, false), runAs); randomBoolean() ? generateRandomStringArray(20, 30, false) : null,
authUser);
BytesStreamOutput output = new BytesStreamOutput(); BytesStreamOutput output = new BytesStreamOutput();
User.writeTo(user, output); User.writeTo(user, output);
@ -48,11 +50,51 @@ public class UserTests extends ESTestCase {
assertThat(readFrom, not(sameInstance(user))); assertThat(readFrom, not(sameInstance(user)));
assertThat(readFrom.principal(), is(user.principal())); assertThat(readFrom.principal(), is(user.principal()));
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true)); assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
assertThat(readFrom.runAs(), is(notNullValue())); User readFromAuthUser = readFrom.authenticatedUser();
User readFromRunAs = readFrom.runAs(); assertThat(authUser, is(notNullValue()));
assertThat(readFromRunAs.principal(), is(runAs.principal())); assertThat(readFromAuthUser.principal(), is(authUser.principal()));
assertThat(Arrays.equals(readFromRunAs.roles(), runAs.roles()), is(true)); assertThat(Arrays.equals(readFromAuthUser.roles(), authUser.roles()), is(true));
assertThat(readFromRunAs.runAs(), is(nullValue())); assertThat(readFromAuthUser.authenticatedUser(), is(authUser));
}
public void testRunAsBackcompatRead() throws Exception {
User user = new User(randomAlphaOfLengthBetween(4, 30),
randomBoolean() ? generateRandomStringArray(20, 30, false) : null);
// store the runAs user as the "authenticationUser" here to mimic old format for writing
User authUser = new User(randomAlphaOfLengthBetween(4, 30), generateRandomStringArray(20, 30, false), user);
BytesStreamOutput output = new BytesStreamOutput();
User.writeTo(authUser, output);
StreamInput input = output.bytes().streamInput();
input.setVersion(randomFrom(Version.V_5_0_0, Version.V_5_4_0_UNRELEASED));
User readFrom = User.readFrom(input);
assertThat(readFrom.principal(), is(user.principal()));
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
User readFromAuthUser = readFrom.authenticatedUser();
assertThat(authUser, is(notNullValue()));
assertThat(readFromAuthUser.principal(), is(authUser.principal()));
assertThat(Arrays.equals(readFromAuthUser.roles(), authUser.roles()), is(true));
}
public void testRunAsBackcompatWrite() throws Exception {
User user = new User(randomAlphaOfLengthBetween(4, 30),
randomBoolean() ? generateRandomStringArray(20, 30, false) : null);
// store the runAs user as the "authenticationUser" here to mimic old format for writing
User authUser = new User(randomAlphaOfLengthBetween(4, 30), generateRandomStringArray(20, 30, false), user);
BytesStreamOutput output = new BytesStreamOutput();
output.setVersion(randomFrom(Version.V_5_0_0, Version.V_5_4_0_UNRELEASED));
User.writeTo(authUser, output);
StreamInput input = output.bytes().streamInput();
User readFrom = User.readFrom(input);
assertThat(readFrom.principal(), is(user.principal()));
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
User readFromAuthUser = readFrom.authenticatedUser();
assertThat(authUser, is(notNullValue()));
assertThat(readFromAuthUser.principal(), is(authUser.principal()));
assertThat(Arrays.equals(readFromAuthUser.roles(), authUser.roles()), is(true));
} }
public void testSystemUserReadAndWrite() throws Exception { public void testSystemUserReadAndWrite() throws Exception {
@ -62,7 +104,7 @@ public class UserTests extends ESTestCase {
User readFrom = User.readFrom(output.bytes().streamInput()); User readFrom = User.readFrom(output.bytes().streamInput());
assertThat(readFrom, is(sameInstance(SystemUser.INSTANCE))); assertThat(readFrom, is(sameInstance(SystemUser.INSTANCE)));
assertThat(readFrom.runAs(), is(nullValue())); assertThat(readFrom.authenticatedUser(), is(SystemUser.INSTANCE));
} }
public void testXPackUserReadAndWrite() throws Exception { public void testXPackUserReadAndWrite() throws Exception {
@ -72,7 +114,7 @@ public class UserTests extends ESTestCase {
User readFrom = User.readFrom(output.bytes().streamInput()); User readFrom = User.readFrom(output.bytes().streamInput());
assertThat(readFrom, is(sameInstance(XPackUser.INSTANCE))); assertThat(readFrom, is(sameInstance(XPackUser.INSTANCE)));
assertThat(readFrom.runAs(), is(nullValue())); assertThat(readFrom.authenticatedUser(), is(XPackUser.INSTANCE));
} }
public void testFakeInternalUserSerialization() throws Exception { public void testFakeInternalUserSerialization() throws Exception {
@ -87,24 +129,14 @@ public class UserTests extends ESTestCase {
} }
} }
public void testCreateUserRunningAsSystemUser() throws Exception {
try {
new User(randomAlphaOfLengthBetween(3, 10),
generateRandomStringArray(16, 30, false), SystemUser.INSTANCE);
fail("should not be able to create a runAs user with the system user");
} catch (ElasticsearchSecurityException e) {
assertThat(e.getMessage(), containsString("invalid run_as user"));
}
}
public void testUserToString() throws Exception { public void testUserToString() throws Exception {
User user = new User("u1", "r1"); User user = new User("u1", "r1");
assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}]")); assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}]"));
user = new User("u1", new String[] { "r1", "r2" }, "user1", "user1@domain.com", Collections.singletonMap("key", "val"), true); user = new User("u1", new String[] { "r1", "r2" }, "user1", "user1@domain.com", Collections.singletonMap("key", "val"), true);
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=user1,email=user1@domain.com,metadata={key=val}]")); assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=user1,email=user1@domain.com,metadata={key=val}]"));
user = new User("u1", new String[] {"r1", "r2"}, new User("u2", "r3")); user = new User("u1", new String[] {"r1"}, new User("u2", "r2", "r3"));
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=null,email=null,metadata={},runAs=[User[username=u2," + assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}," +
"roles=[r3],fullName=null,email=null,metadata={}]]]")); "authenticatedUser=[User[username=u2,roles=[r2,r3],fullName=null,email=null,metadata={}]]]"));
} }
public void testReservedUserSerialization() throws Exception { public void testReservedUserSerialization() throws Exception {

View File

@ -0,0 +1,239 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_user:
username: "inline_template_user"
body: >
{
"password": "changeme",
"roles" : [ "inline_template_role" ]
}
- do:
xpack.security.put_user:
username: "stored_template_user"
body: >
{
"password": "changeme",
"roles" : [ "stored_template_role" ]
}
- do:
xpack.security.put_user:
username: "file_template_user"
body: >
{
"password": "changeme",
"roles" : [ "file_template_role" ]
}
- do:
xpack.security.put_user:
username: "terms_template_user"
body: >
{
"password": "changeme",
"roles" : [ "terms_template_role" ],
"metadata": {
"groups": [ "inline_template_user", "file_template_user" ]
}
}
- do:
xpack.security.put_role:
name: "inline_template_role"
body: >
{
"indices": [
{
"names": "foobar",
"privileges": ["all"],
"query" : {
"template" : {
"inline" : {
"term" : { "username" : "{{_user.username}}" }
}
}
}
}
]
}
- do:
xpack.security.put_role:
name: "terms_template_role"
body: >
{
"indices": [
{
"names": "foobar",
"privileges": ["all"],
"query" : {
"template" : {
"inline" : "{\"terms\" : { \"username\" : {{#toJson}}_user.metadata.groups{{/toJson}} } }"
}
}
}
]
}
- do:
xpack.security.put_role:
name: "stored_template_role"
body: >
{
"indices": [
{
"names": "foobar",
"privileges": ["all"],
"query" : {
"template" : {
"stored" : "1"
}
}
}
]
}
- do:
xpack.security.put_role:
name: "file_template_role"
body: >
{
"indices": [
{
"names": "foobar",
"privileges": ["all"],
"query" : {
"template" : {
"file" : "query"
}
}
}
]
}
- do:
put_template:
id: "1"
body: >
{
"term" : {
"username" : "{{_user.username}}"
}
}
- do:
index:
index: foobar
type: type
id: 1
body: >
{
"username": "inline_template_user"
}
- do:
index:
index: foobar
type: type
id: 2
body: >
{
"username": "stored_template_user"
}
- do:
index:
index: foobar
type: type
id: 3
body: >
{
"username": "file_template_user"
}
- do:
indices.refresh: {}
---
teardown:
- do:
xpack.security.delete_user:
username: "inline_template_user"
ignore: 404
- do:
xpack.security.delete_user:
username: "stored_template_user"
ignore: 404
- do:
xpack.security.delete_user:
username: "file_template_user"
ignore: 404
- do:
xpack.security.delete_user:
username: "terms_template_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "inline_template_role"
ignore: 404
- do:
xpack.security.delete_role:
name: "stored_template_role"
ignore: 404
- do:
xpack.security.delete_role:
name: "file_template_role"
ignore: 404
- do:
xpack.security.delete_role:
name: "terms_template_role"
ignore: 404
---
"Test inline template with run as":
- do:
headers:
es-security-runas-user: "inline_template_user"
search:
index: foobar
body: { "query" : { "match_all" : {} } }
- match: { hits.total: 1}
- match: { hits.hits.0._source.username: inline_template_user}
---
"Test stored template with run as":
- do:
headers:
es-security-runas-user: "stored_template_user"
search:
index: foobar
body: { "query" : { "match_all" : {} } }
- match: { hits.total: 1}
- match: { hits.hits.0._source.username: stored_template_user}
---
"Test file template with run as":
- do:
headers:
es-security-runas-user: "file_template_user"
search:
index: foobar
body: { "query" : { "match_all" : {} } }
- match: { hits.total: 1}
- match: { hits.hits.0._source.username: file_template_user}
---
"Test terms template with run as":
- do:
headers:
es-security-runas-user: "terms_template_user"
search:
index: foobar
body: { "query" : { "match_all" : {} } }
- match: { hits.total: 2}
- match: { hits.hits.0._source.username: inline_template_user}
- match: { hits.hits.1._source.username: file_template_user}