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:
parent
590eea57ac
commit
1c3d907748
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
Loading…
Reference in New Issue