security: default role checks authenticating realm

This change makes the default role check the authenticating realm when authorizing
a request for the current user (or run as user) where the user is trying to change their
own password. We need to do this, otherwise we open up the potential of a user in one
realm changing the password of a user in another realm.

As part of this work, the authentication service has been refactored and simplified. A
new object, Authentication, is now returned when authenticating. Currently, this object
contains the user, authenticating realm information, and if it is a run as request the
information of the realm that looked up the user.

Closes elastic/elasticsearch#2089

Original commit: elastic/x-pack-elasticsearch@3fd9c37a16
This commit is contained in:
jaymode 2016-04-27 11:49:13 -04:00
parent 27958cc708
commit eeb964c886
28 changed files with 1074 additions and 757 deletions

View File

@ -7,9 +7,10 @@ package org.elasticsearch.shield.authz;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.InternalAuthenticationService;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.junit.Before; import org.junit.Before;
@ -33,21 +34,24 @@ public class AuthorizationUtilsTests extends ESTestCase {
public void testSystemUserSwitchWithNullorSystemUser() { public void testSystemUserSwitchWithNullorSystemUser() {
if (randomBoolean()) { if (randomBoolean()) {
threadContext.putTransient(InternalAuthenticationService.USER_KEY, SystemUser.INSTANCE); threadContext.putTransient(Authentication.AUTHENTICATION_KEY,
new Authentication(SystemUser.INSTANCE, new RealmRef("test", "test", "foo"), null));
} }
assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true)); assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true));
} }
public void testSystemUserSwitchWithNonSystemUser() { public void testSystemUserSwitchWithNonSystemUser() {
User user = new User(randomAsciiOfLength(6), new String[] {}); User user = new User(randomAsciiOfLength(6), new String[] {});
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("indices:foo", "cluster:bar")); threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("indices:foo", "cluster:bar"));
assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true)); assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true));
} }
public void testSystemUserSwitchWithNonSystemUserAndInternalAction() { public void testSystemUserSwitchWithNonSystemUserAndInternalAction() {
User user = new User(randomAsciiOfLength(6), new String[] {}); User user = new User(randomAsciiOfLength(6), new String[] {});
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("internal:foo/bar")); threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("internal:foo/bar"));
assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(false)); assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(false));
} }

View File

@ -62,7 +62,7 @@ public abstract class InternalClient extends FilterClient {
try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().stashContext()) { try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().stashContext()) {
try { try {
authcService.attachUserHeaderIfMissing(XPackUser.INSTANCE); authcService.attachUserIfMissing(XPackUser.INSTANCE);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new ElasticsearchException("failed to attach internal user to request", ioe); throw new ElasticsearchException("failed to attach internal user to request", ioe);
} }

View File

@ -8,6 +8,7 @@ package org.elasticsearch.shield;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
@ -26,6 +27,12 @@ public interface SecurityContext {
User getUser(); User getUser();
Authentication getAuthentication();
default boolean hasAuthentication() {
return getAuthentication() != null;
}
class Insecure implements SecurityContext { class Insecure implements SecurityContext {
public static final Insecure INSTANCE = new Insecure(); public static final Insecure INSTANCE = new Insecure();
@ -51,6 +58,11 @@ public interface SecurityContext {
public User getUser() { public User getUser() {
return null; return null;
} }
@Override
public Authentication getAuthentication() {
return null;
}
} }
class Secure implements SecurityContext { class Secure implements SecurityContext {
@ -82,14 +94,20 @@ public interface SecurityContext {
@Override @Override
public User getUser() { public User getUser() {
return authcService.getCurrentUser(); Authentication authentication = authcService.getCurrentAuthentication();
return authentication == null ? null : authentication.getUser();
}
@Override
public Authentication getAuthentication() {
return authcService.getCurrentAuthentication();
} }
private void setUser(User user) { private void setUser(User user) {
try { try {
authcService.attachUserHeaderIfMissing(user); authcService.attachUserIfMissing(user);
} catch (IOException e) { } catch (IOException | IllegalArgumentException e) {
throw new ElasticsearchException("failed to attach watcher user to request", e); throw new ElasticsearchException("failed to attach user to request", e);
} }
} }
} }

View File

@ -20,13 +20,14 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.plugin.core.LicenseUtils; import org.elasticsearch.license.plugin.core.LicenseUtils;
import org.elasticsearch.shield.Security; import org.elasticsearch.shield.Security;
import org.elasticsearch.shield.SecurityContext;
import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.action.ShieldActionMapper;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.action.interceptor.RequestInterceptor; import org.elasticsearch.shield.action.interceptor.RequestInterceptor;
import org.elasticsearch.shield.audit.AuditTrail; import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.InternalAuthenticationService;
import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.authz.AuthorizationUtils; import org.elasticsearch.shield.authz.AuthorizationUtils;
import org.elasticsearch.shield.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.shield.authz.privilege.HealthAndStatsPrivilege;
@ -58,11 +59,13 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
private final Set<RequestInterceptor> requestInterceptors; private final Set<RequestInterceptor> requestInterceptors;
private final SecurityLicenseState licenseState; private final SecurityLicenseState licenseState;
private final ThreadContext threadContext; private final ThreadContext threadContext;
private final SecurityContext securityContext;
@Inject @Inject
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService,
CryptoService cryptoService, AuditTrail auditTrail, SecurityLicenseState licenseState, CryptoService cryptoService, AuditTrail auditTrail, SecurityLicenseState licenseState,
ShieldActionMapper actionMapper, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool) { ShieldActionMapper actionMapper, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool,
SecurityContext securityContext) {
super(settings); super(settings);
this.authcService = authcService; this.authcService = authcService;
this.authzService = authzService; this.authzService = authzService;
@ -72,6 +75,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
this.licenseState = licenseState; this.licenseState = licenseState;
this.requestInterceptors = requestInterceptors; this.requestInterceptors = requestInterceptors;
this.threadContext = threadPool.getThreadContext(); this.threadContext = threadPool.getThreadContext();
this.securityContext = securityContext;
} }
@Override @Override
@ -91,8 +95,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
// only restore the context if it is not empty. This is needed because sometimes a response is sent to the user // only restore the context if it is not empty. This is needed because sometimes a response is sent to the user
// and then a cleanup action is executed (like for search without a scroll) // and then a cleanup action is executed (like for search without a scroll)
final ThreadContext.StoredContext original = threadContext.newStoredContext(); final ThreadContext.StoredContext original = threadContext.newStoredContext();
final boolean restoreOriginalContext = threadContext.getHeader(InternalAuthenticationService.USER_KEY) != null || final boolean restoreOriginalContext = securityContext.hasAuthentication();
threadContext.getTransient(InternalAuthenticationService.USER_KEY) != null;
try { try {
if (licenseState.authenticationAndAuthorizationEnabled()) { if (licenseState.authenticationAndAuthorizationEnabled()) {
if (AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action)) { if (AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action)) {
@ -133,9 +136,11 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
the {@link Rest} filter and the {@link ServerTransport} filter respectively), it's safe to assume a system user the {@link Rest} filter and the {@link ServerTransport} filter respectively), it's safe to assume a system user
here if a request is not associated with any other user. here if a request is not associated with any other user.
*/ */
String shieldAction = actionMapper.action(action, request); final String shieldAction = actionMapper.action(action, request);
User user = authcService.authenticate(shieldAction, request, SystemUser.INSTANCE); Authentication authentication = authcService.authenticate(shieldAction, request, SystemUser.INSTANCE);
authzService.authorize(user, shieldAction, request); assert authentication != null;
authzService.authorize(authentication, shieldAction, request);
final User user = authentication.getUser();
request = unsign(user, shieldAction, request); request = unsign(user, shieldAction, request);
/* /*

View File

@ -94,6 +94,10 @@ public interface AuditTrail {
@Override @Override
public void runAsDenied(User user, String action, TransportMessage message) { public void runAsDenied(User user, String action, TransportMessage message) {
} }
@Override
public void runAsDenied(User user, RestRequest request) {
}
}; };
String name(); String name();
@ -131,4 +135,6 @@ public interface AuditTrail {
void runAsGranted(User user, String action, TransportMessage message); void runAsGranted(User user, String action, TransportMessage message);
void runAsDenied(User user, String action, TransportMessage message); void runAsDenied(User user, String action, TransportMessage message);
void runAsDenied(User user, RestRequest request);
} }

View File

@ -188,4 +188,13 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
} }
} }
} }
@Override
public void runAsDenied(User user, RestRequest request) {
if (securityLicenseState.auditingEnabled()) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.runAsDenied(user, request);
}
}
}
} }

View File

@ -512,7 +512,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
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)) {
try { try {
enqueue(message("run_as_granted", action, user, null, message), "access_granted"); enqueue(message("run_as_granted", action, user, null, message), "run_as_granted");
} catch (Exception e) { } catch (Exception e) {
logger.warn("failed to index audit event: [run_as_granted]", e); logger.warn("failed to index audit event: [run_as_granted]", e);
} }
@ -523,7 +523,18 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
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)) {
try { try {
enqueue(message("run_as_denied", action, user, null, message), "access_granted"); enqueue(message("run_as_denied", action, user, null, message), "run_as_denied");
} catch (Exception e) {
logger.warn("failed to index audit event: [run_as_denied]", e);
}
}
}
@Override
public void runAsDenied(User user, RestRequest request) {
if (events.contains(RUN_AS_DENIED)) {
try {
enqueue(message("run_as_denied", user, request), "run_as_denied");
} catch (Exception e) { } catch (Exception e) {
logger.warn("failed to index audit event: [run_as_denied]", e); logger.warn("failed to index audit event: [run_as_denied]", e);
} }
@ -620,6 +631,26 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
return msg.end(); return msg.end();
} }
private Message message(String type, User user, RestRequest request) throws Exception {
Message msg = new Message().start();
common("rest", type, msg.builder);
msg.builder.field(Field.PRINCIPAL, user.principal());
msg.builder.field(Field.REQUEST_BODY, restRequestContent(request));
msg.builder.field(Field.ORIGIN_TYPE, "rest");
SocketAddress address = request.getRemoteAddress();
if (address instanceof InetSocketAddress) {
msg.builder.field(Field.ORIGIN_ADDRESS, NetworkAddress.format(((InetSocketAddress) request.getRemoteAddress())
.getAddress()));
} else {
msg.builder.field(Field.ORIGIN_ADDRESS, address);
}
msg.builder.field(Field.URI, request.uri());
return msg.end();
}
private Message message(String layer, String type, InetAddress originAddress, String profile, private Message message(String layer, String type, InetAddress originAddress, String profile,
ShieldIpFilterRule rule) throws IOException { ShieldIpFilterRule rule) throws IOException {

View File

@ -384,6 +384,17 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent<LoggingAuditTr
} }
} }
@Override
public void runAsDenied(User user, RestRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("{}[rest] [run_as_denied]\t{}, principal=[{}], uri=[{}], request_body=[{}]", prefix,
hostAttributes(request), user.principal(), request.uri(), restRequestContent(request));
} else {
logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], uri=[{}]", prefix,
hostAttributes(request), user.principal(), request.uri());
}
}
private static String hostAttributes(RestRequest request) { private static String hostAttributes(RestRequest request) {
String formattedAddress; String formattedAddress;
SocketAddress socketAddress = request.getRemoteAddress(); SocketAddress socketAddress = request.getRemoteAddress();

View File

@ -0,0 +1,186 @@
/*
* 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.shield.authc;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.crypto.CryptoService;
import org.elasticsearch.shield.user.User;
import java.io.IOException;
import java.util.Base64;
import java.util.Objects;
public class Authentication {
public static final String AUTHENTICATION_KEY = "_xpack_security_authentication";
private final User user;
private final RealmRef authenticatedBy;
private final RealmRef lookedUpBy;
public Authentication(User user, RealmRef authenticatedBy, RealmRef lookedUpBy) {
this.user = Objects.requireNonNull(user);
this.authenticatedBy = Objects.requireNonNull(authenticatedBy);
this.lookedUpBy = lookedUpBy;
}
public Authentication(StreamInput in) throws IOException {
this.user = User.readFrom(in);
this.authenticatedBy = new RealmRef(in);
if (in.readBoolean()) {
this.lookedUpBy = new RealmRef(in);
} else {
this.lookedUpBy = null;
}
}
public User getUser() {
return user;
}
// TODO remove run as from the User object...
public User getRunAsUser() {
if (user.runAs() != null) {
return user.runAs();
}
return user;
}
public RealmRef getAuthenticatedBy() {
return authenticatedBy;
}
public RealmRef getLookedUpBy() {
return lookedUpBy;
}
public static Authentication readFromContext(ThreadContext ctx, CryptoService cryptoService, boolean sign)
throws IOException, IllegalArgumentException {
Authentication authentication = ctx.getTransient(AUTHENTICATION_KEY);
if (authentication != null) {
assert ctx.getHeader(AUTHENTICATION_KEY) != null;
return authentication;
}
String authenticationHeader = ctx.getHeader(AUTHENTICATION_KEY);
if (authenticationHeader == null) {
return null;
}
return deserializeHeaderAndPutInContext(authenticationHeader, ctx, cryptoService, sign);
}
static Authentication deserializeHeaderAndPutInContext(String header, ThreadContext ctx, CryptoService cryptoService, boolean sign)
throws IOException, IllegalArgumentException {
assert ctx.getTransient(AUTHENTICATION_KEY) == null;
if (sign) {
header = cryptoService.unsignAndVerify(header);
}
byte[] bytes = Base64.getDecoder().decode(header);
StreamInput input = StreamInput.wrap(bytes);
Version version = Version.readVersion(input);
input.setVersion(version);
Authentication authentication = new Authentication(input);
ctx.putTransient(AUTHENTICATION_KEY, authentication);
return authentication;
}
void writeToContextIfMissing(ThreadContext context, CryptoService cryptoService, boolean sign)
throws IOException, IllegalArgumentException {
if (context.getTransient(AUTHENTICATION_KEY) != null) {
if (context.getHeader(AUTHENTICATION_KEY) == null) {
throw new IllegalStateException("authentication present as a transient but not a header");
}
return;
}
if (context.getHeader(AUTHENTICATION_KEY) != null) {
deserializeHeaderAndPutInContext(context.getHeader(AUTHENTICATION_KEY), context, cryptoService, sign);
} else {
writeToContext(context, cryptoService, sign);
}
}
void writeToContext(ThreadContext ctx, CryptoService cryptoService, boolean sign)
throws IOException, IllegalArgumentException {
ensureContextDoesNotContainAuthentication(ctx);
String header = encode();
if (sign) {
header = cryptoService.sign(header);
}
ctx.putTransient(AUTHENTICATION_KEY, this);
ctx.putHeader(AUTHENTICATION_KEY, header);
}
void ensureContextDoesNotContainAuthentication(ThreadContext ctx) {
if (ctx.getTransient(AUTHENTICATION_KEY) != null) {
if (ctx.getHeader(AUTHENTICATION_KEY) == null) {
throw new IllegalStateException("authentication present as a transient but not a header");
}
throw new IllegalStateException("authentication is already present in the context");
}
}
String encode() throws IOException {
BytesStreamOutput output = new BytesStreamOutput();
Version.writeVersion(Version.CURRENT, output);
writeTo(output);
return Base64.getEncoder().encodeToString(output.bytes().toBytes());
}
void writeTo(StreamOutput out) throws IOException {
User.writeTo(user, out);
authenticatedBy.writeTo(out);
if (lookedUpBy != null) {
out.writeBoolean(true);
lookedUpBy.writeTo(out);
} else {
out.writeBoolean(false);
}
}
public static class RealmRef {
private final String nodeName;
private final String name;
private final String type;
public RealmRef(String name, String type, String nodeName) {
this.nodeName = nodeName;
this.name = name;
this.type = type;
}
public RealmRef(StreamInput in) throws IOException {
this.nodeName = in.readString();
this.name = in.readString();
this.type = in.readString();
}
void writeTo(StreamOutput out) throws IOException {
out.writeString(nodeName);
out.writeString(name);
out.writeString(type);
}
public String getNodeName() {
return nodeName;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}
}

View File

@ -23,12 +23,12 @@ public interface AuthenticationService {
* the user and that user is then "attached" to the request's context. * the user and that user is then "attached" to the request's context.
* *
* @param request The request to be authenticated * @param request The request to be authenticated
* @return The authenticated user * @return A object containing the authentication information (user, realm, etc)
* @throws ElasticsearchSecurityException If no user was associated with the request or if the associated * @throws ElasticsearchSecurityException If no user was associated with the request or if the associated
* user credentials were found to be invalid * user credentials were found to be invalid
* @throws IOException If an error occurs when reading or writing * @throws IOException If an error occurs when reading or writing
*/ */
User authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException; Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException;
/** /**
* Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e. * Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e.
@ -43,13 +43,13 @@ public interface AuthenticationService {
* authentication will be based on the whether there's an attached user to in the message and * authentication will be based on the whether there's an attached user to in the message and
* if there is, whether its credentials are valid. * if there is, whether its credentials are valid.
* *
* @return The authenticated user (either the attached one or if there isn't the fallback one if provided) * @return A object containing the authentication information (user, realm, etc)
* *
* @throws ElasticsearchSecurityException If the associated user credentials were found to be invalid or in the * @throws ElasticsearchSecurityException If the associated user credentials were found to be invalid or in the
* case where there was no user associated with the request, if the defautl * case where there was no user associated with the request, if the defautl
* token could not be authenticated. * token could not be authenticated.
*/ */
User authenticate(String action, TransportMessage message, User fallbackUser) throws IOException; Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException;
/** /**
* Checks if there's already a user header attached to the given message. If missing, a new header is * Checks if there's already a user header attached to the given message. If missing, a new header is
@ -57,7 +57,7 @@ public interface AuthenticationService {
* *
* @param user The user to be attached if the header is missing * @param user The user to be attached if the header is missing
*/ */
void attachUserHeaderIfMissing(User user) throws IOException; void attachUserIfMissing(User user) throws IOException, IllegalArgumentException;
User getCurrentUser(); Authentication getCurrentAuthentication();
} }

View File

@ -6,20 +6,18 @@
package org.elasticsearch.shield.authc; package org.elasticsearch.shield.authc;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.node.Node;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.user.AnonymousUser; import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.audit.AuditTrail; import org.elasticsearch.shield.audit.AuditTrail;
@ -28,11 +26,9 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.transport.TransportMessage;
import java.io.IOException; import java.io.IOException;
import java.util.Base64;
import java.util.List; import java.util.List;
import static org.elasticsearch.shield.Security.setting; import static org.elasticsearch.shield.Security.setting;
import static org.elasticsearch.shield.support.Exceptions.authenticationError;
/** /**
* An authentication service that delegates the authentication process to its configured {@link Realm realms}. * An authentication service that delegates the authentication process to its configured {@link Realm realms}.
@ -47,14 +43,12 @@ public class InternalAuthenticationService extends AbstractComponent implements
Setting.boolSetting(setting("authc.run_as.enabled"), true, Property.NodeScope); Setting.boolSetting(setting("authc.run_as.enabled"), true, Property.NodeScope);
public static final String RUN_AS_USER_HEADER = "es-shield-runas-user"; public static final String RUN_AS_USER_HEADER = "es-shield-runas-user";
static final String TOKEN_KEY = "_shield_token";
public static final String USER_KEY = "_shield_user";
private final Realms realms; private final Realms realms;
private final AuditTrail auditTrail; private final AuditTrail auditTrail;
private final CryptoService cryptoService; private final CryptoService cryptoService;
private final AuthenticationFailureHandler failureHandler; private final AuthenticationFailureHandler failureHandler;
private final ThreadContext threadContext; private final ThreadContext threadContext;
private final String nodeName;
private final boolean signUserHeader; private final boolean signUserHeader;
private final boolean runAsEnabled; private final boolean runAsEnabled;
@ -62,6 +56,7 @@ public class InternalAuthenticationService extends AbstractComponent implements
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService, public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService,
AuthenticationFailureHandler failureHandler, ThreadPool threadPool, RestController controller) { AuthenticationFailureHandler failureHandler, ThreadPool threadPool, RestController controller) {
super(settings); super(settings);
this.nodeName = Node.NODE_NAME_SETTING.get(settings);
this.realms = realms; this.realms = realms;
this.auditTrail = auditTrail; this.auditTrail = auditTrail;
this.cryptoService = cryptoService; this.cryptoService = cryptoService;
@ -75,394 +70,331 @@ public class InternalAuthenticationService extends AbstractComponent implements
} }
@Override @Override
public User authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException { public Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException {
return authenticate(newRequest(request), (User) null); return createAuthenticator(request).authenticate();
} }
@Override @Override
public User authenticate(String action, TransportMessage message, User fallbackUser) throws IOException { public Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException {
return authenticate(newRequest(action, message), fallbackUser); return createAuthenticator(action, message, fallbackUser).authenticate();
}
User authenticate(AuditableRequest request, User fallbackUser) throws IOException {
User user = getUserFromContext();
if (user != null) {
return user;
}
String header = threadContext.getHeader(USER_KEY);
if (header != null) {
if (request instanceof Rest) {
request.tamperedRequest();
throw new ElasticsearchSecurityException("rest request attempted to inject a user");
}
if (signUserHeader) {
try {
header = cryptoService.unsignAndVerify(header);
} catch (Exception e) {
request.tamperedRequest();
throw e;
}
}
user = decodeUser(header);
assert user != null;
putUserInContext(user);
} else {
user = authenticateWithRealms(request, fallbackUser);
setUser(user);
}
return user;
} }
@Override @Override
public void attachUserHeaderIfMissing(User user) throws IOException { public void attachUserIfMissing(User user) throws IOException {
if (threadContext.getHeader(USER_KEY) != null) { Authentication authentication = new Authentication(user, new RealmRef("__attach", "__attach", nodeName), null);
return; authentication.writeToContextIfMissing(threadContext, cryptoService, signUserHeader);
}
User transientUser = threadContext.getTransient(USER_KEY);
if (transientUser != null) {
setUserHeader(transientUser);
return;
}
setUser(user);
} }
@Override @Override
public User getCurrentUser() { public Authentication getCurrentAuthentication() {
return getUserFromContext();
}
void setUserHeader(User user) throws IOException {
String userHeader = signUserHeader ? cryptoService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
threadContext.putHeader(USER_KEY, userHeader);
}
void setUser(User user) throws IOException {
putUserInContext(user);
setUserHeader(user);
}
void putUserInContext(User user) {
if (threadContext.getTransient(USER_KEY) != null) {
User ctxUser = threadContext.getTransient(USER_KEY);
throw new IllegalArgumentException("context already has user [" + ctxUser.principal() + "]. trying to set user [" + user
.principal() + "]");
}
threadContext.putTransient(USER_KEY, user);
}
User getUserFromContext() {
return threadContext.getTransient(USER_KEY);
}
static User decodeUser(String text) {
try { try {
byte[] bytes = Base64.getDecoder().decode(text); Authentication authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader);
StreamInput input = StreamInput.wrap(bytes); return authentication == null ? null : authentication;
Version version = Version.readVersion(input); } catch (IOException e) {
input.setVersion(version); logger.error("failed to read authentication", e);
return User.readFrom(input);
} catch (IOException ioe) {
throw authenticationError("could not read authenticated user", ioe);
}
}
static String encodeUser(User user, ESLogger logger) {
try {
BytesStreamOutput output = new BytesStreamOutput();
Version.writeVersion(Version.CURRENT, output);
User.writeTo(user, output);
byte[] bytes = output.bytes().toBytes();
return Base64.getEncoder().encodeToString(bytes);
} catch (IOException ioe) {
if (logger != null) {
logger.error("could not encode authenticated user in message header... falling back to token headers", ioe);
}
return null; return null;
} }
} }
/** Authenticator createAuthenticator(RestRequest request) {
* Authenticates the user associated with the given request by delegating the authentication to return new Authenticator(request);
* the configured realms. Each realm that supports the given token will be asked to perform authentication, }
* the first realm that successfully authenticates will "win" and its authenticated user will be returned.
* If none of the configured realms successfully authenticates the request, an {@link ElasticsearchSecurityException} Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser) {
* will be thrown. return new Authenticator(action, message, fallbackUser);
* <p> }
* The order by which the realms are checked is defined in {@link Realms}.
* class Authenticator {
* @param request the request to authenticate
* @param fallbackUser The user to assume if there is not other user attached to the message private final AuditableRequest request;
* @return The authenticated user private final User fallbackUser;
* @throws ElasticsearchSecurityException If none of the configured realms successfully authenticated the
* request private RealmRef authenticatedBy = null;
*/ private RealmRef lookedupBy = null;
User authenticateWithRealms(AuditableRequest request, User fallbackUser) throws ElasticsearchSecurityException {
AuthenticationToken token; Authenticator(RestRequest request) {
try { this.request = new Rest(request);
token = token(request); this.fallbackUser = null;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("failed to extract token from request: [{}]", e, request);
} else {
logger.warn("failed to extract token from request: [{}]", request, e.getMessage());
}
throw request.exceptionProcessingRequest(e);
} }
if (token == null) { Authenticator(String action, TransportMessage message, User fallbackUser) {
if (fallbackUser != null) { this.request = new Transport(action, message);
return fallbackUser; this.fallbackUser = fallbackUser;
}
Authentication authenticate() throws IOException, IllegalArgumentException {
Authentication existing = getCurrentAuthentication();
if (existing != null) {
return existing;
} }
if (AnonymousUser.enabled()) {
return AnonymousUser.INSTANCE; AuthenticationToken token = extractToken();
if (token == null) {
return handleNullToken();
}
User user = authenticateToken(token);
if (user == null) {
throw handleNullUser(token);
}
user = lookupRunAsUserIfNecessary(user, token);
final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy);
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
return authentication;
}
Authentication getCurrentAuthentication() {
Authentication authentication;
try {
authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader);
} catch (Exception e) {
throw request.tamperedRequest();
}
// make sure this isn't a rest request since we don't allow authentication to be read via a HTTP request...
if (authentication != null && request instanceof Rest) {
throw request.tamperedRequest();
}
return authentication;
}
AuthenticationToken extractToken() {
AuthenticationToken token = null;
try {
for (Realm realm : realms) {
token = realm.token(threadContext);
if (token != null) {
logger.trace("realm [{}] resolved authentication token [{}] from [{}]", realm, token.principal(), request);
break;
}
}
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("failed to extract token from request: [{}]", e, request);
} else {
logger.warn("failed to extract token from request: [{}]", request, e.getMessage());
}
throw request.exceptionProcessingRequest(e, null);
}
return token;
}
Authentication handleNullToken() throws IOException {
Authentication authentication = null;
if (fallbackUser != null) {
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
authentication = new Authentication(fallbackUser, authenticatedBy, null);
} else if (AnonymousUser.enabled()) {
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
authentication = new Authentication(AnonymousUser.INSTANCE, authenticatedBy, null);
}
if (authentication != null) {
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
return authentication;
} }
throw request.anonymousAccessDenied(); throw request.anonymousAccessDenied();
} }
User user; User authenticateToken(AuthenticationToken token) {
try { User user = null;
user = authenticate(request, token); try {
} catch (Exception e) { for (Realm realm : realms) {
if (logger.isDebugEnabled()) { if (realm.supports(token)) {
user = realm.authenticate(token);
if (user != null) {
authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName);
break;
}
request.realmAuthenticationFailed(token, realm.name());
}
}
} catch (Exception e) {
logger.debug("authentication failed for principal [{}], [{}]", e, request); logger.debug("authentication failed for principal [{}], [{}]", e, request);
throw request.exceptionProcessingRequest(e, token);
} finally {
token.clearCredentials();
} }
throw request.exceptionProcessingRequest(e, token); return user;
} }
if (user == null) { ElasticsearchSecurityException handleNullUser(AuthenticationToken token) {
throw request.failedAuthentication(token); throw request.authenticationFailed(token);
} }
if (runAsEnabled) { boolean shouldTryToRunAs(User authenticatedUser, AuthenticationToken token) {
if (runAsEnabled == false) {
return false;
}
String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER); String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER);
if (runAsUsername != null) { if (runAsUsername == null) {
if (runAsUsername.isEmpty()) { return false;
logger.warn("user [{}] attempted to runAs with an empty username", user.principal()); }
throw request.failedAuthentication(token);
} if (runAsUsername.isEmpty()) {
User runAsUser; logger.debug("user [{}] attempted to runAs with an empty username", authenticatedUser.principal());
try { throw request.runAsDenied(new User(authenticatedUser.principal(), authenticatedUser.roles(),
runAsUser = lookupUser(runAsUsername); new User(runAsUsername, Strings.EMPTY_ARRAY)), token);
} catch (Exception e) { }
if (logger.isDebugEnabled()) { return true;
logger.debug("lookup of run as user failed for principal [{}], [{}], run as username [{}]", e, }
token.principal(), request, runAsUsername);
User lookupRunAsUserIfNecessary(User authenticatedUser, AuthenticationToken token) {
User user = authenticatedUser;
if (shouldTryToRunAs(user, token) == false) {
return user;
}
final String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER);
try {
for (Realm realm : realms) {
if (realm.userLookupSupported()) {
User runAsUser = realm.lookupUser(runAsUsername);
if (runAsUser != null) {
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
user = new User(user.principal(), user.roles(), runAsUser);
return user;
}
} }
throw request.exceptionProcessingRequest(e, token);
} }
// wrap in a try catch because the user constructor could throw an exception if we are trying to runAs the system user // the requested run as user does not exist, but we don't throw an error here otherwise this could let
try { // information leak about users in the system... instead we'll just let the authz service fail throw an
if (runAsUser != null) { // authorization error
user = new User(user.principal(), user.roles(), runAsUser); user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY));
} else { } catch (Exception e) {
// the requested run as user does not exist, but we don't throw an error here otherwise this could let logger.debug("run as failed for principal [{}], [{}], run as username [{}]", e, token.principal(), request, runAsUsername);
// information leak about users in the system... instead we'll just let the authz service fail throw an throw request.exceptionProcessingRequest(e, token);
// authorization error
user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY));
}
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("user creation failed for principal [{}], [{}], run as username [{}]", e, token.principal(),
request, runAsUsername);
}
throw request.exceptionProcessingRequest(e, token);
}
} }
return user;
} }
return user;
}
User authenticate(AuditableRequest request, AuthenticationToken token) throws ElasticsearchSecurityException { abstract class AuditableRequest {
assert token != null : "cannot authenticate null tokens";
try {
for (Realm realm : realms) {
if (realm.supports(token)) {
User user = realm.authenticate(token);
if (user != null) {
return user;
}
request.authenticationFailed(token, realm.name());
}
}
request.authenticationFailed(token);
return null;
} finally {
token.clearCredentials();
}
}
AuthenticationToken token(AuditableRequest request) throws ElasticsearchSecurityException { abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
for (Realm realm : realms) {
AuthenticationToken token = realm.token(threadContext);
if (token != null) {
request.tokenResolved(realm.name(), token);
return token;
}
}
return null;
}
User lookupUser(String username) { abstract ElasticsearchSecurityException tamperedRequest();
for (Realm realm : realms) {
if (realm.userLookupSupported()) { abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token);
User user = realm.lookupUser(username);
if (user != null) { abstract ElasticsearchSecurityException authenticationFailed(AuthenticationToken token);
return user;
abstract ElasticsearchSecurityException anonymousAccessDenied();
abstract ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token);
}
class Transport extends AuditableRequest {
private final String action;
private final TransportMessage message;
Transport(String action, TransportMessage message) {
this.action = action;
this.message = message;
}
@Override
void realmAuthenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, action, message);
}
@Override
ElasticsearchSecurityException tamperedRequest() {
auditTrail.tamperedRequest(action, message);
return new ElasticsearchSecurityException("failed to verify signed authentication information");
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) {
if (token != null) {
auditTrail.authenticationFailed(token, action, message);
} else {
auditTrail.authenticationFailed(action, message);
} }
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
}
@Override
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, action, message);
return failureHandler.failedAuthentication(message, token, action, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(action, message);
return failureHandler.missingToken(message, action, threadContext);
}
@Override
ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) {
auditTrail.runAsDenied(user, action, message);
return failureHandler.failedAuthentication(message, token, action, threadContext);
}
public String toString() {
return "transport request action [" + action + "]";
}
}
class Rest extends AuditableRequest {
private final RestRequest request;
Rest(RestRequest request) {
this.request = request;
}
@Override
void realmAuthenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, request);
}
@Override
ElasticsearchSecurityException tamperedRequest() {
auditTrail.tamperedRequest(request);
return new ElasticsearchSecurityException("rest request attempted to inject a user");
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) {
if (token != null) {
auditTrail.authenticationFailed(token, request);
} else {
auditTrail.authenticationFailed(request);
}
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
}
@Override
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, request);
return failureHandler.failedAuthentication(request, token, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(request);
return failureHandler.missingToken(request, threadContext);
}
@Override
ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) {
auditTrail.runAsDenied(user, request);
return failureHandler.failedAuthentication(request, token, threadContext);
}
public String toString() {
return "rest request uri [" + request.uri() + "]";
} }
} }
return null;
} }
public static void addSettings(List<Setting<?>> settings) { public static void addSettings(List<Setting<?>> settings) {
settings.add(SIGN_USER_HEADER); settings.add(SIGN_USER_HEADER);
settings.add(RUN_AS_ENABLED); settings.add(RUN_AS_ENABLED);
} }
// these methods are package private for testing. They are also needed so that a AuditableRequest can be created in tests
AuditableRequest newRequest(String action, TransportMessage message) {
return new Transport(action, message);
}
AuditableRequest newRequest(RestRequest request) {
return new Rest(request);
}
abstract class AuditableRequest {
abstract void authenticationFailed(AuthenticationToken token);
abstract void authenticationFailed(AuthenticationToken token, String realm);
abstract void tamperedRequest();
abstract void tokenResolved(String realm, AuthenticationToken token);
abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e);
abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token);
abstract ElasticsearchSecurityException failedAuthentication(AuthenticationToken token);
abstract ElasticsearchSecurityException anonymousAccessDenied();
}
class Transport extends AuditableRequest {
private final String action;
private final TransportMessage message;
Transport(String action, TransportMessage message) {
this.action = action;
this.message = message;
}
@Override
void authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, action, message);
}
@Override
void authenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, action, message);
}
@Override
void tamperedRequest() {
auditTrail.tamperedRequest(action, message);
}
@Override
void tokenResolved(String realm, AuthenticationToken token) {
logger.trace("realm [{}] resolved authentication token [{}] from transport request with action [{}]",
realm, token.principal(), action);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e) {
auditTrail.authenticationFailed(action, message);
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token) {
authenticationFailed(token);
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
}
ElasticsearchSecurityException failedAuthentication(AuthenticationToken token) {
auditTrail.authenticationFailed(token, action, message);
return failureHandler.failedAuthentication(message, token, action, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(action, message);
return failureHandler.missingToken(message, action, threadContext);
}
public String toString() {
return "transport action [" + action + "]";
}
}
class Rest extends AuditableRequest {
private final RestRequest request;
Rest(RestRequest request) {
this.request = request;
}
@Override
void authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, request);
}
@Override
void authenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, request);
}
@Override
void tamperedRequest() {
auditTrail.tamperedRequest(request);
}
@Override
void tokenResolved(String realm, AuthenticationToken token) {
logger.trace("realm [{}] resolved authentication token [{}] from rest request with uri [{}]",
realm, token.principal(), request.uri());
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e) {
auditTrail.authenticationFailed(request);
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token) {
authenticationFailed(token);
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
}
ElasticsearchSecurityException failedAuthentication(AuthenticationToken token) {
auditTrail.authenticationFailed(token, request);
return failureHandler.failedAuthentication(request, token, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(request);
return failureHandler.missingToken(request, threadContext);
}
public String toString() {
return "rest uri [" + request.uri() + "]";
}
}
} }

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authz; package org.elasticsearch.shield.authz;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
@ -29,11 +30,11 @@ public interface AuthorizationService {
* have the appropriate privileges for this action/request, an {@link ElasticsearchSecurityException} * have the appropriate privileges for this action/request, an {@link ElasticsearchSecurityException}
* will be thrown. * will be thrown.
* *
* @param user The user * @param authentication The authentication information
* @param action The action * @param action The action
* @param request The request * @param request The request
* @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request * @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request
*/ */
void authorize(User user, String action, TransportRequest request) throws ElasticsearchSecurityException; void authorize(Authentication authentication, String action, TransportRequest request) throws ElasticsearchSecurityException;
} }

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authz; package org.elasticsearch.shield.authz;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.InternalAuthenticationService; import org.elasticsearch.shield.authc.InternalAuthenticationService;
@ -44,8 +45,8 @@ public final class AuthorizationUtils {
return false; return false;
} }
User user = threadContext.getTransient(InternalAuthenticationService.USER_KEY); Authentication authentication = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
if (user == null || SystemUser.is(user)) { if (authentication == null || SystemUser.is(authentication.getUser())) {
return true; return true;
} }

View File

@ -23,11 +23,11 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.search.action.SearchTransportService; import org.elasticsearch.search.action.SearchTransportService;
import org.elasticsearch.shield.ShieldTemplateService; import org.elasticsearch.shield.ShieldTemplateService;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.user.AnonymousUser; import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
@ -140,32 +140,32 @@ public class InternalAuthorizationService extends AbstractComponent implements A
} }
@Override @Override
public void authorize(User user, String action, TransportRequest request) throws ElasticsearchSecurityException { public void authorize(Authentication authentication, String action, TransportRequest request) throws ElasticsearchSecurityException {
// prior to doing any authorization lets set the originating action in the context only // prior to doing any authorization lets set the originating action in the context only
setOriginatingAction(action); setOriginatingAction(action);
User effectiveUser = user;
// 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(user)) { if (SystemUser.is(authentication.getRunAsUser())) {
if (SystemUser.isAuthorized(action)) { if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) {
setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL);
grant(user, action, request); grant(authentication, action, request);
return; return;
} }
throw denial(user, action, request); throw denial(authentication, action, request);
} }
GlobalPermission permission = permission(user.roles()); // get the roles of the authenticated user, which may be different than the effective
GlobalPermission permission = permission(authentication.getUser().roles());
final boolean isRunAs = user.runAs() != null; final boolean isRunAs = authentication.getUser() != authentication.getRunAsUser();
// permission can be null as it might be that the user's role // permission can be null as it might be that the user's role
// is unknown // is unknown
if (permission == null || permission.isEmpty()) { if (permission == null || permission.isEmpty()) {
if (isRunAs) { if (isRunAs) {
// the request is a run as request so we should call the specific audit event for a denied run as attempt // the request is a run as request so we should call the specific audit event for a denied run as attempt
throw denyRunAs(user, action, request); throw denyRunAs(authentication, action, request);
} else { } else {
throw denial(user, action, request); throw denial(authentication, action, request);
} }
} }
@ -173,18 +173,17 @@ public class InternalAuthorizationService extends AbstractComponent implements A
if (isRunAs) { if (isRunAs) {
// first we must authorize for the RUN_AS action // first we must authorize for the RUN_AS action
RunAsPermission runAs = permission.runAs(); RunAsPermission runAs = permission.runAs();
if (runAs != null && runAs.check(user.runAs().principal())) { if (runAs != null && runAs.check(authentication.getRunAsUser().principal())) {
grantRunAs(user, action, request); grantRunAs(authentication, action, request);
permission = permission(user.runAs().roles()); permission = permission(authentication.getRunAsUser().roles());
// permission can be null as it might be that the user's role // permission can be null as it might be that the user's role
// is unknown // is unknown
if (permission == null || permission.isEmpty()) { if (permission == null || permission.isEmpty()) {
throw denial(user, action, request); throw denial(authentication, action, request);
} }
effectiveUser = user.runAs();
} else { } else {
throw denyRunAs(user, action, request); throw denyRunAs(authentication, action, request);
} }
} }
@ -193,17 +192,17 @@ public class InternalAuthorizationService extends AbstractComponent implements A
if (ClusterPrivilege.ACTION_MATCHER.test(action)) { if (ClusterPrivilege.ACTION_MATCHER.test(action)) {
ClusterPermission cluster = permission.cluster(); ClusterPermission cluster = permission.cluster();
// we use the effectiveUser for permission checking since we are running as a user! // we use the effectiveUser for permission checking since we are running as a user!
if (cluster != null && cluster.check(action, request, effectiveUser)) { if (cluster != null && cluster.check(action, request, authentication)) {
setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL);
grant(user, action, request); grant(authentication, action, request);
return; return;
} }
throw denial(user, action, request); throw denial(authentication, action, request);
} }
// ok... this is not a cluster action, let's verify it's an indices action // ok... this is not a cluster action, let's verify it's an indices action
if (!IndexPrivilege.ACTION_MATCHER.test(action)) { if (!IndexPrivilege.ACTION_MATCHER.test(action)) {
throw denial(user, action, request); throw denial(authentication, action, request);
} }
// some APIs are indices requests that are not actually associated with indices. For example, // some APIs are indices requests that are not actually associated with indices. For example,
@ -217,33 +216,34 @@ public class InternalAuthorizationService extends AbstractComponent implements A
//note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any //note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any
//indices permission as it's categorized under cluster. This is why the scroll check is performed //indices permission as it's categorized under cluster. This is why the scroll check is performed
//even before checking if the user has any indices permission. //even before checking if the user has any indices permission.
grant(user, action, request); grant(authentication, action, request);
return; return;
} }
assert false : "only scroll related requests are known indices api that don't support retrieving the indices they relate to"; assert false : "only scroll related requests are known indices api that don't support retrieving the indices they relate to";
throw denial(user, action, request); throw denial(authentication, action, request);
} }
if (permission.indices() == null || permission.indices().isEmpty()) { if (permission.indices() == null || permission.indices().isEmpty()) {
throw denial(user, action, request); throw denial(authentication, action, request);
} }
ClusterState clusterState = clusterService.state(); ClusterState clusterState = clusterService.state();
Set<String> indexNames = resolveIndices(user, action, request, clusterState); Set<String> indexNames = resolveIndices(authentication, action, request, clusterState);
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";
MetaData metaData = clusterState.metaData(); MetaData metaData = clusterState.metaData();
IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData); IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData);
if (!indicesAccessControl.isGranted()) { if (!indicesAccessControl.isGranted()) {
throw denial(user, action, request); throw denial(authentication, action, request);
} else if (indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME) != null } else if (indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME) != null
&& indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME).isGranted() && indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME).isGranted()
&& XPackUser.is(user) == false && XPackUser.is(authentication.getRunAsUser()) == false
&& MONITOR_INDEX_PREDICATE.test(action) == false) { && MONITOR_INDEX_PREDICATE.test(action) == 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 [{}]", user.principal(), action, // FIXME its not just the XPackUser. We said the elastic user and superusers could access this!
ShieldTemplateService.SECURITY_INDEX_NAME); logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]",
throw denial(user, action, request); authentication.getRunAsUser().principal(), action, ShieldTemplateService.SECURITY_INDEX_NAME);
throw denial(authentication, action, request);
} else { } else {
setIndicesAccessControl(indicesAccessControl); setIndicesAccessControl(indicesAccessControl);
} }
@ -259,14 +259,14 @@ public class InternalAuthorizationService extends AbstractComponent implements A
} }
indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData); indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData);
if (!indicesAccessControl.isGranted()) { if (!indicesAccessControl.isGranted()) {
throw denial(user, "indices:admin/aliases", request); throw denial(authentication, "indices:admin/aliases", request);
} }
// no need to re-add the indicesAccessControl in the context, // no need to re-add the indicesAccessControl in the context,
// because the create index call doesn't do anything FLS or DLS // because the create index call doesn't do anything FLS or DLS
} }
} }
grant(user, action, request); grant(authentication, action, request);
} }
private void setIndicesAccessControl(IndicesAccessControl accessControl) { private void setIndicesAccessControl(IndicesAccessControl accessControl) {
@ -304,15 +304,15 @@ public class InternalAuthorizationService extends AbstractComponent implements A
return roles.build(); return roles.build();
} }
private Set<String> resolveIndices(User user, String action, TransportRequest request, ClusterState clusterState) { private Set<String> resolveIndices(Authentication authentication, String action, TransportRequest request, ClusterState clusterState) {
MetaData metaData = clusterState.metaData(); MetaData metaData = clusterState.metaData();
for (IndicesAndAliasesResolver resolver : indicesAndAliasesResolvers) { for (IndicesAndAliasesResolver resolver : indicesAndAliasesResolvers) {
if (resolver.requestType().isInstance(request)) { if (resolver.requestType().isInstance(request)) {
return resolver.resolve(user, action, request, metaData); return resolver.resolve(authentication.getRunAsUser(), action, request, metaData);
} }
} }
assert false : "we should be able to resolve indices for any known request that requires indices privileges"; assert false : "we should be able to resolve indices for any known request that requires indices privileges";
throw denial(user, action, request); throw denial(authentication, action, request);
} }
private static boolean isScrollRelatedAction(String action) { private static boolean isScrollRelatedAction(String action) {
@ -325,34 +325,36 @@ public class InternalAuthorizationService extends AbstractComponent implements A
action.equals(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); action.equals(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME);
} }
private ElasticsearchSecurityException denial(User user, String action, TransportRequest request) { private ElasticsearchSecurityException denial(Authentication authentication, String action, TransportRequest request) {
auditTrail.accessDenied(user, action, request); auditTrail.accessDenied(authentication.getUser(), action, request);
return denialException(user, action); return denialException(authentication, action);
} }
private ElasticsearchSecurityException denyRunAs(User user, String action, TransportRequest request) { private ElasticsearchSecurityException denyRunAs(Authentication authentication, String action, TransportRequest request) {
auditTrail.runAsDenied(user, action, request); auditTrail.runAsDenied(authentication.getUser(), action, request);
return denialException(user, action); return denialException(authentication, action);
} }
private void grant(User user, String action, TransportRequest request) { private void grant(Authentication authentication, String action, TransportRequest request) {
auditTrail.accessGranted(user, action, request); auditTrail.accessGranted(authentication.getUser(), action, request);
} }
private void grantRunAs(User user, String action, TransportRequest request) { private void grantRunAs(Authentication authentication, String action, TransportRequest request) {
auditTrail.runAsGranted(user, action, request); auditTrail.runAsGranted(authentication.getUser(), action, request);
} }
private ElasticsearchSecurityException denialException(User user, String action) { private ElasticsearchSecurityException denialException(Authentication authentication, String action) {
final User user = authentication.getUser();
// Special case for anonymous user // Special case for anonymous user
if (AnonymousUser.enabled() && AnonymousUser.is(user)) { if (AnonymousUser.enabled() && AnonymousUser.is(user)) {
if (anonymousAuthzExceptionEnabled == false) { if (anonymousAuthzExceptionEnabled == false) {
throw authcFailureHandler.authenticationRequired(action, threadContext); throw authcFailureHandler.authenticationRequired(action, threadContext);
} }
} }
if (user.runAs() != null) { // check for run as
if (user != authentication.getRunAsUser()) {
return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(), return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(),
user.runAs().principal()); authentication.getRunAsUser().principal());
} }
return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal()); return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal());
} }

View File

@ -5,8 +5,8 @@
*/ */
package org.elasticsearch.shield.authz.permission; package org.elasticsearch.shield.authz.permission;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authz.privilege.ClusterPrivilege; import org.elasticsearch.shield.authz.privilege.ClusterPrivilege;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
import java.util.List; import java.util.List;
@ -17,13 +17,13 @@ import java.util.function.Predicate;
*/ */
public interface ClusterPermission extends Permission { public interface ClusterPermission extends Permission {
boolean check(String action, TransportRequest request, User user); boolean check(String action, TransportRequest request, Authentication authentication);
public static class Core implements ClusterPermission { public static class Core implements ClusterPermission {
public static final Core NONE = new Core(ClusterPrivilege.NONE) { public static final Core NONE = new Core(ClusterPrivilege.NONE) {
@Override @Override
public boolean check(String action, TransportRequest request, User user) { public boolean check(String action, TransportRequest request, Authentication authentication) {
return false; return false;
} }
@ -46,7 +46,7 @@ public interface ClusterPermission extends Permission {
} }
@Override @Override
public boolean check(String action, TransportRequest request, User user) { public boolean check(String action, TransportRequest request, Authentication authentication) {
return predicate.test(action); return predicate.test(action);
} }
@ -65,7 +65,7 @@ public interface ClusterPermission extends Permission {
} }
@Override @Override
public boolean check(String action, TransportRequest request, User user) { public boolean check(String action, TransportRequest request, Authentication authentication) {
if (globals == null) { if (globals == null) {
return false; return false;
} }
@ -73,7 +73,7 @@ public interface ClusterPermission extends Permission {
if (global == null || global.cluster() == null) { if (global == null || global.cluster() == null) {
throw new RuntimeException(); throw new RuntimeException();
} }
if (global.cluster().check(action, request, user)) { if (global.cluster().check(action, request, authentication)) {
return true; return true;
} }
} }

View File

@ -5,7 +5,9 @@
*/ */
package org.elasticsearch.shield.authz.permission; package org.elasticsearch.shield.authz.permission;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.esnative.NativeRealm;
import org.elasticsearch.shield.authc.esnative.ReservedRealm;
import org.elasticsearch.shield.action.user.AuthenticateAction; import org.elasticsearch.shield.action.user.AuthenticateAction;
import org.elasticsearch.shield.action.user.ChangePasswordAction; import org.elasticsearch.shield.action.user.ChangePasswordAction;
import org.elasticsearch.shield.action.user.UserRequest; import org.elasticsearch.shield.action.user.UserRequest;
@ -40,18 +42,47 @@ public class DefaultRole extends Role {
} }
@Override @Override
public boolean check(String action, TransportRequest request, User user) { public boolean check(String action, TransportRequest request, Authentication authentication) {
final boolean actionAllowed = super.check(action, request, user); final boolean actionAllowed = super.check(action, request, authentication);
if (actionAllowed) { if (actionAllowed) {
assert request instanceof UserRequest; if (request instanceof UserRequest == false) {
assert false : "right now only a user request should be allowed";
return false;
}
UserRequest userRequest = (UserRequest) request; UserRequest userRequest = (UserRequest) request;
String[] usernames = userRequest.usernames(); String[] usernames = userRequest.usernames();
assert usernames != null && usernames.length == 1; if (usernames == null || usernames.length != 1 || usernames[0] == null) {
assert false : "this role should only be used for actions to apply to a single user";
return false;
}
final String username = usernames[0]; final String username = usernames[0];
assert username != null; final boolean sameUsername = authentication.getRunAsUser().principal().equals(username);
return user.principal().equals(username); if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
return checkChangePasswordAction(authentication);
}
assert AuthenticateAction.NAME.equals(action) || sameUsername == false;
return sameUsername;
} }
return false; return false;
} }
} }
static boolean checkChangePasswordAction(Authentication authentication) {
// 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
// and do malicious things
final boolean isRunAs = authentication.getUser() != authentication.getRunAsUser();
final String realmType;
if (isRunAs) {
realmType = authentication.getLookedUpBy().getType();
} else {
realmType = authentication.getAuthenticatedBy().getType();
}
assert realmType != null;
// ensure the user was authenticated by a realm that we can change a password for. The native realm is an internal realm and right
// now only one can exist in the realm configuration - if this changes we should update this check
return ReservedRealm.TYPE.equals(realmType) || NativeRealm.TYPE.equals(realmType);
}
} }

View File

@ -56,7 +56,7 @@ public interface ClientTransportFilter {
the system user will be attached. There cannot be a request outgoing from this the system user will be attached. There cannot be a request outgoing from this
node that is not associated with a user. node that is not associated with a user.
*/ */
authcService.attachUserHeaderIfMissing(SystemUser.INSTANCE); authcService.attachUserIfMissing(SystemUser.INSTANCE);
} }
} }
} }

View File

@ -8,7 +8,7 @@ package org.elasticsearch.shield.transport;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.action.ShieldActionMapper;
import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.pki.PkiRealm; import org.elasticsearch.shield.authc.pki.PkiRealm;
@ -101,8 +101,8 @@ public interface ServerTransportFilter {
} }
} }
User user = authcService.authenticate(shieldAction, request, null); Authentication authentication = authcService.authenticate(shieldAction, request, null);
authzService.authorize(user, shieldAction, request); authzService.authorize(authentication, shieldAction, request);
} }
} }

View File

@ -12,7 +12,10 @@ import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.SecurityContext;
import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.action.ShieldActionMapper;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.audit.AuditTrail; import org.elasticsearch.shield.audit.AuditTrail;
@ -62,7 +65,7 @@ public class ShieldActionFilterTests extends ESTestCase {
ThreadPool threadPool = mock(ThreadPool.class); ThreadPool threadPool = mock(ThreadPool.class);
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY)); when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
filter = new ShieldActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, securityLicenseState, filter = new ShieldActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, securityLicenseState,
new ShieldActionMapper(), new HashSet<>(), threadPool); new ShieldActionMapper(), new HashSet<>(), threadPool, mock(SecurityContext.class));
} }
public void testApply() throws Exception { public void testApply() throws Exception {
@ -71,10 +74,11 @@ public class ShieldActionFilterTests extends ESTestCase {
ActionFilterChain chain = mock(ActionFilterChain.class); ActionFilterChain chain = mock(ActionFilterChain.class);
Task task = mock(Task.class); Task task = mock(Task.class);
User user = new User("username", "r1", "r2"); User user = new User("username", "r1", "r2");
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
doReturn(request).when(spy(filter)).unsign(user, "_action", request); doReturn(request).when(spy(filter)).unsign(user, "_action", request);
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);
verify(authzService).authorize(user, "_action", request); verify(authzService).authorize(authentication, "_action", request);
verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class)); verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class));
} }
@ -85,8 +89,9 @@ public class ShieldActionFilterTests extends ESTestCase {
RuntimeException exception = new RuntimeException("process-error"); RuntimeException exception = new RuntimeException("process-error");
Task task = mock(Task.class); Task task = mock(Task.class);
User user = new User("username", "r1", "r2"); User user = new User("username", "r1", "r2");
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
doThrow(exception).when(authzService).authorize(user, "_action", request); when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
doThrow(exception).when(authzService).authorize(authentication, "_action", request);
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);
verify(listener).onFailure(exception); verify(listener).onFailure(exception);
verifyNoMoreInteractions(chain); verifyNoMoreInteractions(chain);
@ -98,12 +103,13 @@ public class ShieldActionFilterTests extends ESTestCase {
ActionFilterChain chain = mock(ActionFilterChain.class); ActionFilterChain chain = mock(ActionFilterChain.class);
User user = mock(User.class); User user = mock(User.class);
Task task = mock(Task.class); Task task = mock(Task.class);
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
when(cryptoService.signed("signed_scroll_id")).thenReturn(true); when(cryptoService.signed("signed_scroll_id")).thenReturn(true);
when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id"); when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);
assertThat(request.scrollId(), equalTo("scroll_id")); assertThat(request.scrollId(), equalTo("scroll_id"));
verify(authzService).authorize(user, "_action", request); verify(authzService).authorize(authentication, "_action", request);
verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class)); verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class));
} }
@ -114,7 +120,8 @@ public class ShieldActionFilterTests extends ESTestCase {
IllegalArgumentException sigException = new IllegalArgumentException("bad bad boy"); IllegalArgumentException sigException = new IllegalArgumentException("bad bad boy");
User user = mock(User.class); User user = mock(User.class);
Task task = mock(Task.class); Task task = mock(Task.class);
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(user); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
when(cryptoService.signed("scroll_id")).thenReturn(true); when(cryptoService.signed("scroll_id")).thenReturn(true);
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id"); doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);

View File

@ -7,7 +7,6 @@ package org.elasticsearch.shield.authc;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -15,7 +14,8 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.authc.InternalAuthenticationService.AuditableRequest; import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.authc.InternalAuthenticationService.Authenticator;
import org.elasticsearch.shield.SecurityLicenseState.EnabledRealmType; import org.elasticsearch.shield.SecurityLicenseState.EnabledRealmType;
import org.elasticsearch.shield.user.AnonymousUser; import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
@ -32,17 +32,14 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.transport.TransportMessage;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import static org.elasticsearch.shield.support.Exceptions.authenticationError; import static org.elasticsearch.shield.support.Exceptions.authenticationError;
import static org.elasticsearch.test.ShieldTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.ShieldTestsUtils.assertAuthenticationException;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -50,11 +47,10 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
@ -66,9 +62,6 @@ import static org.mockito.Mockito.when;
*/ */
public class InternalAuthenticationServiceTests extends ESTestCase { public class InternalAuthenticationServiceTests extends ESTestCase {
@Rule
public ExpectedException thrown = ExpectedException.none();
InternalAuthenticationService service; InternalAuthenticationService service;
TransportMessage message; TransportMessage message;
RestRequest restRequest; RestRequest restRequest;
@ -88,10 +81,15 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
message = new InternalMessage(); message = new InternalMessage();
restRequest = new FakeRestRequest(); restRequest = new FakeRestRequest();
firstRealm = mock(Realm.class); firstRealm = mock(Realm.class);
when(firstRealm.name()).thenReturn("file"); when(firstRealm.type()).thenReturn("file");
when(firstRealm.name()).thenReturn("file_realm");
secondRealm = mock(Realm.class); secondRealm = mock(Realm.class);
when(secondRealm.name()).thenReturn("second"); when(secondRealm.type()).thenReturn("second");
Settings settings = Settings.builder().put("path.home", createTempDir()).build(); when(secondRealm.name()).thenReturn("second_realm");
Settings settings = Settings.builder()
.put("path.home", createTempDir())
.put("node.name", "authc_test")
.build();
SecurityLicenseState shieldLicenseState = mock(SecurityLicenseState.class); SecurityLicenseState shieldLicenseState = mock(SecurityLicenseState.class);
when(shieldLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL); when(shieldLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL);
when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true); when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true);
@ -113,7 +111,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
threadContext = new ThreadContext(Settings.EMPTY); threadContext = new ThreadContext(Settings.EMPTY);
controller = mock(RestController.class); controller = mock(RestController.class);
when(threadPool.getThreadContext()).thenReturn(threadContext); when(threadPool.getThreadContext()).thenReturn(threadContext);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, when(cryptoService.sign(any(String.class))).thenReturn("_signed_auth");
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
} }
@ -127,17 +126,21 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.token(threadContext)).thenReturn(null); when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.token(threadContext)).thenReturn(token);
AuthenticationToken result = service.token(service.newRequest("_action", message)); Authenticator authenticator = service.createAuthenticator("_action", message, null);
AuthenticationToken result = authenticator.extractToken();
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result, is(token)); assertThat(result, is(token));
verifyZeroInteractions(auditTrail); verifyZeroInteractions(auditTrail);
} }
public void testTokenMissing() throws Exception { public void testTokenMissing() throws Exception {
AuthenticationToken token = service.token(service.newRequest("_action", message)); Authenticator authenticator = service.createAuthenticator("_action", message, null);
AuthenticationToken token = authenticator.extractToken();
assertThat(token, nullValue()); assertThat(token, nullValue());
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, authenticator::handleNullToken);
assertThat(e.getMessage(), containsString("missing authentication token"));
verify(auditTrail).anonymousAccessDenied("_action", message);
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), nullValue());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -147,20 +150,20 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.authenticate(token)).thenReturn(null); // first fails when(firstRealm.authenticate(token)).thenReturn(null); // first fails
when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(user); when(secondRealm.authenticate(token)).thenReturn(user);
if (randomBoolean()) {
when(firstRealm.token(threadContext)).thenReturn(token);
} else {
when(secondRealm.token(threadContext)).thenReturn(token);
}
service = spy(service); Authentication result = service.authenticate("_action", message, null);
doReturn(token).when(service).token(any(AuditableRequest.class));
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result, is(user)); assertThat(result.getUser(), is(user));
verify(auditTrail).authenticationFailed("file", token, "_action", message); assertThat(result.getLookedUpBy(), is(nullValue()));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
assertThat(user1, notNullValue()); verify(auditTrail).authenticationFailed(firstRealm.name(), token, "_action", message);
assertThat(user1, sameInstance(user)); verify(cryptoService).sign(any(String.class));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user")); assertThreadContextContainsAuthentication(result);
} }
public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception { public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception {
@ -168,36 +171,28 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.supports(token)).thenReturn(false); when(firstRealm.supports(token)).thenReturn(false);
when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(user); when(secondRealm.authenticate(token)).thenReturn(user);
when(secondRealm.token(threadContext)).thenReturn(token);
service = spy(service); Authentication result = service.authenticate("_action", message, null);
doReturn(token).when(service).token(any(AuditableRequest.class));
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result, is(user)); assertThat(result.getUser(), is(user));
verifyZeroInteractions(auditTrail); verifyZeroInteractions(auditTrail);
verify(firstRealm, never()).authenticate(token); verify(firstRealm, never()).authenticate(token);
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThreadContextContainsAuthentication(result);
assertThat(user1, notNullValue());
assertThat(user1, is((Object) user));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
} }
public void testAuthenticateCached() throws Exception { public void testAuthenticateCached() throws Exception {
User user = new User("_username", "r1"); final Authentication authentication = new Authentication(new User("_username", "r1"), new RealmRef("test", "cached", "foo"), null);
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user); authentication.writeToContext(threadContext, cryptoService, true);
User result = service.authenticate("_action", message, null);
Authentication result = service.authenticate("_action", message, null);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result, is(user)); assertThat(result, is(authentication));
verifyZeroInteractions(auditTrail); verifyZeroInteractions(auditTrail);
verifyZeroInteractions(firstRealm); verifyZeroInteractions(firstRealm);
verifyZeroInteractions(secondRealm); verifyZeroInteractions(secondRealm);
verifyZeroInteractions(cryptoService); verify(cryptoService).sign(any(String.class));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user1, notNullValue());
assertThat(user1, is(user));
} }
public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationException() throws Exception { public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationException() throws Exception {
@ -214,36 +209,30 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testTokenRestMissing() throws Exception { public void testTokenRestMissing() throws Exception {
when(firstRealm.token(threadContext)).thenReturn(null); when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(null); when(secondRealm.token(threadContext)).thenReturn(null);
AuthenticationToken token = service.token(service.newRequest(restRequest));
Authenticator authenticator = service.createAuthenticator(restRequest);
AuthenticationToken token = authenticator.extractToken();
assertThat(token, nullValue()); assertThat(token, nullValue());
} }
public void testEncodeDecodeUser() throws Exception { public void authenticationInContextAndHeader() throws Exception {
User user = new User("username", "r1", "r2", "r3");
String text = InternalAuthenticationService.encodeUser(user, null);
User user2 = InternalAuthenticationService.decodeUser(text);
assertThat(user, equalTo(user2));
text = InternalAuthenticationService.encodeUser(SystemUser.INSTANCE, null);
user2 = InternalAuthenticationService.decodeUser(text);
assertThat(SystemUser.INSTANCE, sameInstance(user2));
}
public void testUserHeader() throws Exception {
User user = new User("_username", "r1"); User user = new User("_username", "r1");
when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.token(threadContext)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true); when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user); when(firstRealm.authenticate(token)).thenReturn(user);
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
service = spy(service); Authentication result = service.authenticate("_action", message, null);
AuditableRequest request = service.newRequest("_action", message);
doReturn(token).when(service).token(request);
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result, is(user)); assertThat(result.getUser(), is(user));
String userStr = threadContext.getHeader(InternalAuthenticationService.USER_KEY);
String userStr = threadContext.getHeader(Authentication.AUTHENTICATION_KEY);
assertThat(userStr, notNullValue()); assertThat(userStr, notNullValue());
assertThat(userStr, equalTo("_signed_user")); assertThat(userStr, equalTo("_signed_auth"));
Authentication ctxAuth = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
assertThat(ctxAuth, is(result));
} }
public void testAuthenticateTransportAnonymous() throws Exception { public void testAuthenticateTransportAnonymous() throws Exception {
@ -276,38 +265,24 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.token(threadContext)).thenReturn(null); when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(null); when(secondRealm.token(threadContext)).thenReturn(null);
User user1 = new User("username", "r1", "r2"); User user1 = new User("username", "r1", "r2");
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, user1); Authentication result = service.authenticate("_action", message, user1);
assertThat(user1, sameInstance(user2)); assertThat(result, notNullValue());
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThat(result.getUser(), sameInstance(user1));
assertThat(user3, sameInstance(user2)); assertThreadContextContainsAuthentication(result);
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
} }
public void testAuthenticateTransportSuccessNoFallback() throws Exception { public void testAuthenticateTransportSuccess() throws Exception {
User user1 = new User("username", "r1", "r2"); User user = new User("username", "r1", "r2");
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.token(threadContext)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true); when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1); when(firstRealm.authenticate(token)).thenReturn(user);
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, null);
assertThat(user1, sameInstance(user2));
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user3, sameInstance(user2));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo("_signed_user"));
}
public void testAuthenticateTransportSuccessWithFallback() throws Exception { Authentication result = service.authenticate("_action", message, fallback);
User user1 = new User("username", "r1", "r2"); assertThat(result, notNullValue());
when(firstRealm.token(threadContext)).thenReturn(token); assertThat(result.getUser(), sameInstance(user));
when(firstRealm.supports(token)).thenReturn(true); assertThreadContextContainsAuthentication(result);
when(firstRealm.authenticate(token)).thenReturn(user1);
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
User user2 = service.authenticate("_action", message, SystemUser.INSTANCE);
assertThat(user1, sameInstance(user2));
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user3, sameInstance((Object) user2));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
} }
public void testAuthenticateRestSuccess() throws Exception { public void testAuthenticateRestSuccess() throws Exception {
@ -315,10 +290,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.token(threadContext)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true); when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1); when(firstRealm.authenticate(token)).thenReturn(user1);
User user2 = service.authenticate(restRequest); Authentication result = service.authenticate(restRequest);
assertThat(user1, sameInstance(user2)); assertThat(result, notNullValue());
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThat(result.getUser(), sameInstance(user1));
assertThat(user3, sameInstance(user2)); assertThreadContextContainsAuthentication(result);
} }
public void testAutheticateTransportContextAndHeader() throws Exception { public void testAutheticateTransportContextAndHeader() throws Exception {
@ -326,12 +301,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.token(threadContext)).thenReturn(token);
when(firstRealm.supports(token)).thenReturn(true); when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.authenticate(token)).thenReturn(user1); when(firstRealm.authenticate(token)).thenReturn(user1);
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user"); Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
User user2 = service.authenticate("_action", message, SystemUser.INSTANCE); assertThat(authentication, notNullValue());
assertThat(user1, sameInstance(user2)); assertThat(authentication.getUser(), sameInstance(user1));
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThreadContextContainsAuthentication(authentication);
assertThat(user3, sameInstance(user2));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
reset(firstRealm); reset(firstRealm);
// checking authentication from the context // checking authentication from the context
@ -341,10 +314,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putTransient(InternalAuthenticationService.USER_KEY, threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
threadContext.getTransient(InternalAuthenticationService.USER_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
User user = service.authenticate("_action", message1, SystemUser.INSTANCE); Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
assertThat(user, sameInstance(user1)); assertThat(ctxAuth, sameInstance(authentication));
verifyZeroInteractions(firstRealm); verifyZeroInteractions(firstRealm);
reset(firstRealm); reset(firstRealm);
@ -354,8 +327,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1); when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putHeader(InternalAuthenticationService.USER_KEY, threadContext.getHeader(InternalAuthenticationService.USER_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
when(cryptoService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null)); when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode());
BytesStreamOutput output = new BytesStreamOutput(); BytesStreamOutput output = new BytesStreamOutput();
threadContext1.writeTo(output); threadContext1.writeTo(output);
@ -366,12 +339,13 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1); when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE); Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
assertThat(user, equalTo(user1)); assertThat(result, notNullValue());
assertThat(result.getUser(), equalTo(user1));
verifyZeroInteractions(firstRealm); verifyZeroInteractions(firstRealm);
} }
public void testAutheticateTransportContextAndHeaderNoSigning() throws Exception { public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception {
Settings settings = Settings.builder().put(InternalAuthenticationService.SIGN_USER_HEADER.getKey(), false).build(); Settings settings = Settings.builder().put(InternalAuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
@ -380,12 +354,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.supports(token)).thenReturn(true); when(firstRealm.supports(token)).thenReturn(true);
when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.token(threadContext)).thenReturn(token);
when(firstRealm.authenticate(token)).thenReturn(user1); when(firstRealm.authenticate(token)).thenReturn(user1);
User user2 = service.authenticate("_action", message, SystemUser.INSTANCE); Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
assertThat(user1, sameInstance(user2)); assertThat(authentication, notNullValue());
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThat(authentication.getUser(), sameInstance(user1));
assertThat(user3, sameInstance(user2)); assertThreadContextContainsAuthentication(authentication, false);
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY),
equalTo((Object) InternalAuthenticationService.encodeUser(user1, null)));
reset(firstRealm); reset(firstRealm);
// checking authentication from the context // checking authentication from the context
@ -394,17 +366,16 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1); when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putTransient(InternalAuthenticationService.USER_KEY, threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
threadContext.getTransient(InternalAuthenticationService.USER_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
User user = service.authenticate("_action", message1, SystemUser.INSTANCE); Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
assertThat(user, sameInstance(user1)); assertThat(ctxAuth, sameInstance(authentication));
verifyZeroInteractions(firstRealm); verifyZeroInteractions(firstRealm);
reset(firstRealm); reset(firstRealm);
// checking authentication from the user header // checking authentication from the user header
threadContext1 = new ThreadContext(Settings.EMPTY); threadContext1 = new ThreadContext(Settings.EMPTY);
threadContext1.putHeader(InternalAuthenticationService.USER_KEY, threadContext.getHeader(InternalAuthenticationService.USER_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
BytesStreamOutput output = new BytesStreamOutput(); BytesStreamOutput output = new BytesStreamOutput();
threadContext1.writeTo(output); threadContext1.writeTo(output);
@ -415,8 +386,9 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1); when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE); Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
assertThat(user, equalTo(user1)); assertThat(result, notNullValue());
assertThat(result.getUser(), equalTo(user1));
verifyZeroInteractions(firstRealm); verifyZeroInteractions(firstRealm);
verifyZeroInteractions(cryptoService); verifyZeroInteractions(cryptoService);
@ -424,8 +396,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testAuthenticateTamperedUser() throws Exception { public void testAuthenticateTamperedUser() throws Exception {
InternalMessage message = new InternalMessage(); InternalMessage message = new InternalMessage();
threadContext.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user"); threadContext.putHeader(Authentication.AUTHENTICATION_KEY, "_signed_auth");
when(cryptoService.unsignAndVerify("_signed_user")).thenThrow( when(cryptoService.unsignAndVerify("_signed_auth")).thenThrow(
randomFrom(new RuntimeException(), new IllegalArgumentException(), new IllegalStateException())); randomFrom(new RuntimeException(), new IllegalArgumentException(), new IllegalStateException()));
try { try {
@ -444,23 +416,26 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
} else { } else {
user = new User("username", "r1", "r2"); user = new User("username", "r1", "r2");
} }
assertThat(threadContext.getTransient(InternalAuthenticationService.USER_KEY), nullValue()); assertThat(threadContext.getTransient(Authentication.AUTHENTICATION_KEY), nullValue());
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), nullValue()); assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), nullValue());
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user"); service.attachUserIfMissing(user);
service.attachUserHeaderIfMissing(user);
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); Authentication authentication = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
assertThat(user1, sameInstance((Object) user)); assertThat(authentication, notNullValue());
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user")); assertThat(authentication.getUser(), sameInstance((Object) user));
assertThat(authentication.getLookedUpBy(), nullValue());
assertThat(authentication.getAuthenticatedBy().getName(), is("__attach"));
assertThat(authentication.getAuthenticatedBy().getType(), is("__attach"));
assertThat(authentication.getAuthenticatedBy().getNodeName(), is("authc_test"));
assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), equalTo((Object) "_signed_auth"));
} }
public void testAttachIfMissingExists() throws Exception { public void testAttachIfMissingExists() throws Exception {
User user = new User("username", "r1", "r2"); Authentication authentication = new Authentication(new User("username", "r1", "r2"), new RealmRef("test", "test", "foo"), null);
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
threadContext.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user"); threadContext.putHeader(Authentication.AUTHENTICATION_KEY, "_signed_auth");
service.attachUserHeaderIfMissing(new User("username2", "r3", "r4")); service.attachUserIfMissing(new User("username2", "r3", "r4"));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThreadContextContainsAuthentication(authentication);
assertThat(user1, sameInstance(user));
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo("_signed_user"));
} }
public void testAnonymousUserRest() throws Exception { public void testAnonymousUserRest() throws Exception {
@ -474,16 +449,13 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
AnonymousUser.initialize(settings); AnonymousUser.initialize(settings);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(), service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
threadPool, controller); threadPool, controller);
RestRequest request = new FakeRestRequest(); RestRequest request = new FakeRestRequest();
User user = service.authenticate(request); Authentication result = service.authenticate(request);
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user1, notNullValue()); assertThat(result, notNullValue());
assertThat(user1, sameInstance((Object) user)); assertThat(result.getUser(), sameInstance((Object) AnonymousUser.INSTANCE));
assertThat(user, notNullValue()); assertThreadContextContainsAuthentication(result);
assertThat(user.principal(), equalTo(username));
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
} }
public void testAnonymousUserTransportNoDefaultUser() throws Exception { public void testAnonymousUserTransportNoDefaultUser() throws Exception {
@ -493,13 +465,12 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
AnonymousUser.initialize(settings); AnonymousUser.initialize(settings);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool, controller); new DefaultAuthenticationFailureHandler(), threadPool, controller);
InternalMessage message = new InternalMessage(); InternalMessage message = new InternalMessage();
User user = service.authenticate("_action", message, null); Authentication result = service.authenticate("_action", message, null);
assertThat(user, notNullValue()); assertThat(result, notNullValue());
assertThat(user.principal(), equalTo(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME)); assertThat(result.getUser(), sameInstance(AnonymousUser.INSTANCE));
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3")); assertThreadContextContainsAuthentication(result);
} }
public void testAnonymousUserTransportWithDefaultUser() throws Exception { public void testAnonymousUserTransportWithDefaultUser() throws Exception {
@ -512,9 +483,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
InternalMessage message = new InternalMessage(); InternalMessage message = new InternalMessage();
User user = service.authenticate("_action", message, SystemUser.INSTANCE); Authentication result = service.authenticate("_action", message, SystemUser.INSTANCE);
assertThat(user, notNullValue()); assertThat(result, notNullValue());
assertThat(user, sameInstance(SystemUser.INSTANCE)); assertThat(result.getUser(), sameInstance(SystemUser.INSTANCE));
assertThreadContextContainsAuthentication(result);
} }
public void testRealmTokenThrowingException() throws Exception { public void testRealmTokenThrowingException() throws Exception {
@ -638,7 +610,14 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(secondRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"})); when(secondRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
when(secondRealm.userLookupSupported()).thenReturn(true); when(secondRealm.userLookupSupported()).thenReturn(true);
User authenticated = service.authenticate("_action", message, null); Authentication result;
if (randomBoolean()) {
result = service.authenticate("_action", message, null);
} else {
result = service.authenticate(restRequest);
}
assertThat(result, notNullValue());
User authenticated = result.getUser();
assertThat(SystemUser.is(authenticated), is(false)); assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.runAs(), is(notNullValue())); assertThat(authenticated.runAs(), is(notNullValue()));
@ -646,29 +625,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
assertThat(authenticated.roles(), arrayContaining("user")); assertThat(authenticated.roles(), arrayContaining("user"));
assertThat(authenticated.runAs().principal(), is("looked up user")); assertThat(authenticated.runAs().principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role")); assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThreadContextContainsAuthentication(result);
assertThat(user1, sameInstance(authenticated));
}
public void testRunAsLookupSameRealmRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as");
when(secondRealm.token(threadContext)).thenReturn(token);
when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
when(secondRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
when(secondRealm.userLookupSupported()).thenReturn(true);
User authenticated = service.authenticate(restRequest);
assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.runAs(), is(notNullValue()));
assertThat(authenticated.principal(), is("lookup user"));
assertThat(authenticated.roles(), arrayContaining("user"));
assertThat(authenticated.runAs().principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user1, sameInstance(authenticated));
} }
public void testRunAsLookupDifferentRealm() throws Exception { public void testRunAsLookupDifferentRealm() throws Exception {
@ -681,7 +638,14 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"})); when(firstRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
when(firstRealm.userLookupSupported()).thenReturn(true); when(firstRealm.userLookupSupported()).thenReturn(true);
User authenticated = service.authenticate("_action", message, null); Authentication result;
if (randomBoolean()) {
result = service.authenticate("_action", message, null);
} else {
result = service.authenticate(restRequest);
}
assertThat(result, notNullValue());
User authenticated = result.getUser();
assertThat(SystemUser.is(authenticated), is(false)); assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.runAs(), is(notNullValue())); assertThat(authenticated.runAs(), is(notNullValue()));
@ -689,74 +653,60 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
assertThat(authenticated.roles(), arrayContaining("user")); assertThat(authenticated.roles(), arrayContaining("user"));
assertThat(authenticated.runAs().principal(), is("looked up user")); assertThat(authenticated.runAs().principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role")); assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY); assertThreadContextContainsAuthentication(result);
assertThat(user1, sameInstance(authenticated));
}
public void testRunAsLookupDifferentRealmRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as");
when(secondRealm.token(threadContext)).thenReturn(token);
when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
when(firstRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
when(firstRealm.userLookupSupported()).thenReturn(true);
User authenticated = service.authenticate(restRequest);
assertThat(SystemUser.is(authenticated), is(false));
assertThat(authenticated.runAs(), is(notNullValue()));
assertThat(authenticated.principal(), is("lookup user"));
assertThat(authenticated.roles(), arrayContaining("user"));
assertThat(authenticated.runAs().principal(), is("looked up user"));
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
assertThat(user1, sameInstance(authenticated));
} }
public void testRunAsWithEmptyRunAsUsernameRest() throws Exception { public void testRunAsWithEmptyRunAsUsernameRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class); AuthenticationToken token = mock(AuthenticationToken.class);
User user = new User("lookup user", new String[]{"user"});
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ""); threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "");
when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.token(threadContext)).thenReturn(token);
when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); when(secondRealm.authenticate(token)).thenReturn(user);
when(secondRealm.userLookupSupported()).thenReturn(true); when(secondRealm.userLookupSupported()).thenReturn(true);
try { try {
service.authenticate(restRequest); service.authenticate(restRequest);
fail("exception should be thrown"); fail("exception should be thrown");
} catch (ElasticsearchException e) { } catch (ElasticsearchException e) {
verify(auditTrail).authenticationFailed(token, restRequest); verify(auditTrail).runAsDenied(any(User.class), eq(restRequest));
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
} }
} }
public void testRunAsWithEmptyRunAsUsername() throws Exception { public void testRunAsWithEmptyRunAsUsername() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class); AuthenticationToken token = mock(AuthenticationToken.class);
User user = new User("lookup user", new String[]{"user"});
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ""); threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "");
when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.token(threadContext)).thenReturn(token);
when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.supports(token)).thenReturn(true);
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); when(secondRealm.authenticate(token)).thenReturn(user);
when(secondRealm.userLookupSupported()).thenReturn(true); when(secondRealm.userLookupSupported()).thenReturn(true);
try { try {
service.authenticate("_action", message, null); service.authenticate("_action", message, null);
fail("exception should be thrown"); fail("exception should be thrown");
} catch (ElasticsearchException e) { } catch (ElasticsearchException e) {
verify(auditTrail).authenticationFailed(token, "_action", message); verify(auditTrail).runAsDenied(any(User.class), eq("_action"), eq(message));
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
} }
} }
public void testVersionWrittenWithUser() throws Exception {
User user = new User("username", "r1", "r2", "r3");
String text = InternalAuthenticationService.encodeUser(user, null);
StreamInput input = StreamInput.wrap(Base64.getDecoder().decode(text));
Version version = Version.readVersion(input);
assertThat(version, is(Version.CURRENT));
}
private static class InternalMessage extends TransportMessage { private static class InternalMessage extends TransportMessage {
} }
void assertThreadContextContainsAuthentication(Authentication authentication) throws IOException {
assertThreadContextContainsAuthentication(authentication, true);
}
void assertThreadContextContainsAuthentication(Authentication authentication, boolean sign) throws IOException {
Authentication contextAuth = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
assertThat(contextAuth, notNullValue());
assertThat(contextAuth, is(authentication));
if (sign) {
assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), equalTo((Object) "_signed_auth"));
} else {
assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), equalTo((Object) authentication.encode()));
}
}
} }

View File

@ -56,6 +56,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.search.action.SearchTransportService; import org.elasticsearch.search.action.SearchTransportService;
import org.elasticsearch.shield.ShieldTemplateService; import org.elasticsearch.shield.ShieldTemplateService;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.user.AnonymousUser; import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.shield.user.SystemUser; import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
@ -123,10 +125,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
// A failure would throw an exception // A failure would throw an exception
internalAuthorizationService.authorize(SystemUser.INSTANCE, "indices:monitor/whatever", request); internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:monitor/whatever", request);
verify(auditTrail).accessGranted(SystemUser.INSTANCE, "indices:monitor/whatever", request); verify(auditTrail).accessGranted(SystemUser.INSTANCE, "indices:monitor/whatever", request);
internalAuthorizationService.authorize(SystemUser.INSTANCE, "internal:whatever", request); internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "internal:whatever", request);
verify(auditTrail).accessGranted(SystemUser.INSTANCE, "internal:whatever", request); verify(auditTrail).accessGranted(SystemUser.INSTANCE, "internal:whatever", request);
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
} }
@ -134,7 +136,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
public void testIndicesActionsAreNotAuthorized() { public void testIndicesActionsAreNotAuthorized() {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
try { try {
internalAuthorizationService.authorize(SystemUser.INSTANCE, "indices:", request); internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:", request);
fail("action beginning with indices should have failed"); fail("action beginning with indices should have failed");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, assertAuthorizationException(e,
@ -147,7 +149,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
public void testClusterAdminActionsAreNotAuthorized() { public void testClusterAdminActionsAreNotAuthorized() {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
try { try {
internalAuthorizationService.authorize(SystemUser.INSTANCE, "cluster:admin/whatever", request); internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/whatever", request);
fail("action beginning with cluster:admin/whatever should have failed"); fail("action beginning with cluster:admin/whatever should have failed");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, assertAuthorizationException(e,
@ -160,7 +162,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
public void testClusterAdminSnapshotStatusActionIsNotAuthorized() { public void testClusterAdminSnapshotStatusActionIsNotAuthorized() {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
try { try {
internalAuthorizationService.authorize(SystemUser.INSTANCE, "cluster:admin/snapshot/status", request); internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/snapshot/status", request);
fail("action beginning with cluster:admin/snapshot/status should have failed"); fail("action beginning with cluster:admin/snapshot/status should have failed");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [cluster:admin/snapshot/status] is unauthorized for user [" + assertAuthorizationException(e, containsString("action [cluster:admin/snapshot/status] is unauthorized for user [" +
@ -174,7 +176,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
TransportRequest request = new SearchRequest(); TransportRequest request = new SearchRequest();
User user = new User("test user"); User user = new User("test user");
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("user without roles should be denied"); fail("user without roles should be denied");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
@ -187,7 +189,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
TransportRequest request = new SearchRequest(); TransportRequest request = new SearchRequest();
User user = new User("test user", "non-existent-role"); User user = new User("test user", "non-existent-role");
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("user with unknown role only should have been denied"); fail("user with unknown role only should have been denied");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
@ -202,7 +204,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build());
try { try {
internalAuthorizationService.authorize(user, "whatever", request); internalAuthorizationService.authorize(createAuthentication(user), "whatever", request);
fail("non indices and non cluster requests should be denied"); fail("non indices and non cluster requests should be denied");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [whatever] is unauthorized for user [test user]")); assertAuthorizationException(e, containsString("action [whatever] is unauthorized for user [test user]"));
@ -217,7 +219,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(rolesStore.role("no_indices")).thenReturn(Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build()); when(rolesStore.role("no_indices")).thenReturn(Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build());
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("user only has cluster roles so indices requests should fail"); fail("user only has cluster roles so indices requests should fail");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
@ -231,28 +233,29 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build());
ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
internalAuthorizationService.authorize(user, ClearScrollAction.NAME, clearScrollRequest); internalAuthorizationService.authorize(createAuthentication(user), ClearScrollAction.NAME, clearScrollRequest);
verify(auditTrail).accessGranted(user, ClearScrollAction.NAME, clearScrollRequest); verify(auditTrail).accessGranted(user, ClearScrollAction.NAME, clearScrollRequest);
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(); SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
internalAuthorizationService.authorize(user, SearchScrollAction.NAME, searchScrollRequest); internalAuthorizationService.authorize(createAuthentication(user), SearchScrollAction.NAME, searchScrollRequest);
verify(auditTrail).accessGranted(user, SearchScrollAction.NAME, searchScrollRequest); verify(auditTrail).accessGranted(user, SearchScrollAction.NAME, searchScrollRequest);
// We have to use a mock request for other Scroll actions as the actual requests are package private to SearchTransportService // We have to use a mock request for other Scroll actions as the actual requests are package private to SearchTransportService
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
internalAuthorizationService.authorize(user, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); internalAuthorizationService
.authorize(createAuthentication(user), SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request);
verify(auditTrail).accessGranted(user, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request);
internalAuthorizationService.authorize(user, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request);
verify(auditTrail).accessGranted(user, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request);
internalAuthorizationService.authorize(user, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request);
verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request);
internalAuthorizationService.authorize(user, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_SCROLL_ACTION_NAME, request);
verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request);
internalAuthorizationService.authorize(user, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request);
verify(auditTrail).accessGranted(user, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request);
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
} }
@ -266,7 +269,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("indices request for b should be denied since there is no such index"); fail("indices request for b should be denied since there is no such index");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
@ -287,7 +290,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
try { try {
internalAuthorizationService.authorize(user, CreateIndexAction.NAME, request); internalAuthorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request);
fail("indices creation request with alias should be denied since user does not have permission to alias"); fail("indices creation request with alias should be denied since user does not have permission to alias");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, assertAuthorizationException(e,
@ -308,7 +311,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(clusterService.state()).thenReturn(state); when(clusterService.state()).thenReturn(state);
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
internalAuthorizationService.authorize(user, CreateIndexAction.NAME, request); internalAuthorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request);
verify(auditTrail).accessGranted(user, CreateIndexAction.NAME, request); verify(auditTrail).accessGranted(user, CreateIndexAction.NAME, request);
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
@ -364,7 +367,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
try { try {
internalAuthorizationService.authorize(AnonymousUser.INSTANCE, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(AnonymousUser.INSTANCE), "indices:a", request);
fail("indices request for b should be denied since there is no such index"); fail("indices request for b should be denied since there is no such index");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, assertAuthorizationException(e,
@ -395,7 +398,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
try { try {
internalAuthorizationService.authorize(anonymousUser, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(anonymousUser), "indices:a", request);
fail("indices request for b should be denied since there is no such index"); fail("indices request for b should be denied since there is no such index");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthenticationException(e, containsString("action [indices:a] requires authentication")); assertAuthenticationException(e, containsString("action [indices:a] requires authentication"));
@ -411,7 +414,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
User user = new User("test user", null, new User("run as me", new String[] { "admin" })); User user = new User("test user", null, new User("run as me", new String[] { "admin" }));
assertThat(user.runAs(), is(notNullValue())); assertThat(user.runAs(), is(notNullValue()));
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("user without roles should be denied for run as"); fail("user without roles should be denied for run as");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
@ -431,7 +434,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
.build()); .build());
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("user without roles should be denied for run as"); fail("user without roles should be denied for run as");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
@ -465,7 +468,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
} }
try { try {
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
fail("the run as user's role doesn't exist so they should not get authorized"); fail("the run as user's role doesn't exist so they should not get authorized");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
@ -496,7 +499,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
.add(IndexPrivilege.ALL, "b") .add(IndexPrivilege.ALL, "b")
.build()); .build());
internalAuthorizationService.authorize(user, "indices:a", request); internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
verify(auditTrail).runAsGranted(user, "indices:a", request); verify(auditTrail).runAsGranted(user, "indices:a", request);
verify(auditTrail).accessGranted(user, "indices:a", request); verify(auditTrail).accessGranted(user, "indices:a", request);
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
@ -533,7 +536,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
String action = requestTuple.v1(); String action = requestTuple.v1();
TransportRequest request = requestTuple.v2(); TransportRequest request = requestTuple.v2();
try { try {
internalAuthorizationService.authorize(user, action, request); internalAuthorizationService.authorize(createAuthentication(user), action, request);
fail("only the xpack user can execute operation [" + action + "] against the internal index"); fail("only the xpack user can execute operation [" + action + "] against the internal index");
} catch (ElasticsearchSecurityException e) { } catch (ElasticsearchSecurityException e) {
assertAuthorizationException(e, containsString("action [" + action + "] is unauthorized for user [all_access_user]")); assertAuthorizationException(e, containsString("action [" + action + "] is unauthorized for user [all_access_user]"));
@ -544,12 +547,12 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
// we should allow waiting for the health of the index or any index if the user has this permission // we should allow waiting for the health of the index or any index if the user has this permission
ClusterHealthRequest request = new ClusterHealthRequest(ShieldTemplateService.SECURITY_INDEX_NAME); ClusterHealthRequest request = new ClusterHealthRequest(ShieldTemplateService.SECURITY_INDEX_NAME);
internalAuthorizationService.authorize(user, ClusterHealthAction.NAME, request); internalAuthorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request);
verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request);
// multiple indices // multiple indices
request = new ClusterHealthRequest(ShieldTemplateService.SECURITY_INDEX_NAME, "foo", "bar"); request = new ClusterHealthRequest(ShieldTemplateService.SECURITY_INDEX_NAME, "foo", "bar");
internalAuthorizationService.authorize(user, ClusterHealthAction.NAME, request); internalAuthorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request);
verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request);
} }
@ -580,7 +583,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
for (Tuple<String, ? extends TransportRequest> requestTuple : requests) { for (Tuple<String, ? extends TransportRequest> requestTuple : requests) {
String action = requestTuple.v1(); String action = requestTuple.v1();
TransportRequest request = requestTuple.v2(); TransportRequest request = requestTuple.v2();
internalAuthorizationService.authorize(user, action, request); internalAuthorizationService.authorize(createAuthentication(user), action, request);
verify(auditTrail).accessGranted(user, action, request); verify(auditTrail).accessGranted(user, action, request);
} }
} }
@ -612,8 +615,13 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
for (Tuple<String, TransportRequest> requestTuple : requests) { for (Tuple<String, TransportRequest> requestTuple : requests) {
String action = requestTuple.v1(); String action = requestTuple.v1();
TransportRequest request = requestTuple.v2(); TransportRequest request = requestTuple.v2();
internalAuthorizationService.authorize(XPackUser.INSTANCE, action, request); internalAuthorizationService.authorize(createAuthentication(XPackUser.INSTANCE), action, request);
verify(auditTrail).accessGranted(XPackUser.INSTANCE, action, request); verify(auditTrail).accessGranted(XPackUser.INSTANCE, action, request);
} }
} }
private Authentication createAuthentication(User user) {
RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by");
return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy);
}
} }

View File

@ -12,6 +12,14 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction; import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.shield.action.user.AuthenticateRequestBuilder; import org.elasticsearch.shield.action.user.AuthenticateRequestBuilder;
import org.elasticsearch.shield.action.user.ChangePasswordRequestBuilder; import org.elasticsearch.shield.action.user.ChangePasswordRequestBuilder;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.Authentication.RealmRef;
import org.elasticsearch.shield.authc.activedirectory.ActiveDirectoryRealm;
import org.elasticsearch.shield.authc.esnative.NativeRealm;
import org.elasticsearch.shield.authc.esnative.ReservedRealm;
import org.elasticsearch.shield.authc.file.FileRealm;
import org.elasticsearch.shield.authc.ldap.LdapRealm;
import org.elasticsearch.shield.authc.pki.PkiRealm;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.action.user.AuthenticateAction; import org.elasticsearch.shield.action.user.AuthenticateAction;
import org.elasticsearch.shield.action.user.AuthenticateRequest; import org.elasticsearch.shield.action.user.AuthenticateRequest;
@ -28,7 +36,11 @@ import java.util.Iterator;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/** /**
* Unit tests for the {@link DefaultRole} * Unit tests for the {@link DefaultRole}
@ -51,9 +63,16 @@ public class DefaultRoleTests extends ESTestCase {
new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request() : new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request() :
new AuthenticateRequestBuilder(mock(Client.class)).username(user.principal()).request(); new AuthenticateRequestBuilder(mock(Client.class)).username(user.principal()).request();
final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME;
assertThat(request, instanceOf(UserRequest.class)); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user), is(true)); assertThat(request, instanceOf(UserRequest.class));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true));
} }
public void testDefaultRoleDoesNotAllowNonMatchingUsername() { public void testDefaultRoleDoesNotAllowNonMatchingUsername() {
@ -64,19 +83,33 @@ public class DefaultRoleTests extends ESTestCase {
new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() :
new AuthenticateRequestBuilder(mock(Client.class)).username(username).request(); new AuthenticateRequestBuilder(mock(Client.class)).username(username).request();
final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME;
assertThat(request, instanceOf(UserRequest.class)); final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user), is(false)); assertThat(request, instanceOf(UserRequest.class));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
final User user2 = new User("admin", new String[] { "bar" }, user); final User user2 = new User("admin", new String[] { "bar" }, user);
when(authentication.getUser()).thenReturn(user2);
when(authentication.getRunAsUser()).thenReturn(user);
final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(lookedUpBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12));
// this should still fail since the username is still different
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
if (request instanceof ChangePasswordRequest) { if (request instanceof ChangePasswordRequest) {
((ChangePasswordRequest)request).username("joe"); ((ChangePasswordRequest)request).username("joe");
} else { } else {
((AuthenticateRequest)request).username("joe"); ((AuthenticateRequest)request).username("joe");
} }
// run as should not be checked by this role, it is up to the caller to provide the correct user assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user2), is(false));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user), is(true));
} }
public void testDefaultRoleDoesNotAllowOtherActions() { public void testDefaultRoleDoesNotAllowOtherActions() {
@ -84,8 +117,85 @@ public class DefaultRoleTests extends ESTestCase {
final TransportRequest request = mock(TransportRequest.class); final TransportRequest request = mock(TransportRequest.class);
final String action = randomFrom(PutUserAction.NAME, DeleteUserAction.NAME, ClusterHealthAction.NAME, ClusterStateAction.NAME, final String action = randomFrom(PutUserAction.NAME, DeleteUserAction.NAME, ClusterHealthAction.NAME, ClusterStateAction.NAME,
ClusterStatsAction.NAME, GetLicenseAction.NAME); ClusterStatsAction.NAME, GetLicenseAction.NAME);
final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(randomBoolean() ? user : new User("runAs"));
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType())
.thenReturn(randomAsciiOfLengthBetween(4, 12));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user), is(false)); assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
verifyZeroInteractions(user, request); verifyZeroInteractions(user, request, authentication);
}
public void testDefaultRoleWithRunAsChecksAuthenticatedBy() {
final String username = "joe";
final User runAs = new User(username);
final User user = new User("admin", new String[] { "bar" }, runAs);
final boolean changePasswordRequest = randomBoolean();
final TransportRequest request = changePasswordRequest ?
new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() :
new AuthenticateRequestBuilder(mock(Client.class)).username(username).request();
final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME;
final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(runAs);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(lookedUpBy.getType())
.thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true));
when(authentication.getRunAsUser()).thenReturn(user);
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
}
public void testDefaultRoleDoesNotAllowChangePasswordForOtherRealms() {
final User user = new User("joe");
final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request();
final String action = ChangePasswordAction.NAME;
final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()).thenReturn(randomFrom(LdapRealm.TYPE, FileRealm.TYPE, ActiveDirectoryRealm.TYPE, PkiRealm.TYPE,
randomAsciiOfLengthBetween(4, 12)));
assertThat(request, instanceOf(UserRequest.class));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
verify(authenticatedBy).getType();
verify(authentication, times(2)).getRunAsUser();
verify(authentication).getUser();
verify(authentication).getAuthenticatedBy();
verifyNoMoreInteractions(authenticatedBy, authentication);
}
public void testDefaultRoleDoesNotAllowChangePasswordForLookedUpByOtherRealms() {
final User runAs = new User("joe");
final User user = new User("admin", new String[] { "bar" }, runAs);
final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(runAs.principal()).request();
final String action = ChangePasswordAction.NAME;
final Authentication authentication = mock(Authentication.class);
final RealmRef authenticatedBy = mock(RealmRef.class);
final RealmRef lookedUpBy = mock(RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getRunAsUser()).thenReturn(runAs);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authentication.getLookedUpBy()).thenReturn(lookedUpBy);
when(lookedUpBy.getType()).thenReturn(randomFrom(LdapRealm.TYPE, FileRealm.TYPE, ActiveDirectoryRealm.TYPE, PkiRealm.TYPE,
randomAsciiOfLengthBetween(4, 12)));
assertThat(request, instanceOf(UserRequest.class));
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
verify(authentication).getLookedUpBy();
verify(authentication, times(2)).getRunAsUser();
verify(authentication).getUser();
verify(lookedUpBy).getType();
verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy);
} }
} }

View File

@ -17,14 +17,14 @@ import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateActio
import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteAction;
import org.elasticsearch.action.index.IndexAction; import org.elasticsearch.action.index.IndexAction;
import org.elasticsearch.marvel.action.MonitoringBulkAction; import org.elasticsearch.marvel.action.MonitoringBulkAction;
import org.elasticsearch.shield.user.KibanaUser; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
import java.util.Arrays; import java.util.Arrays;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
/** /**
* Tests for the kibana role * Tests for the kibana role
@ -32,15 +32,15 @@ import static org.hamcrest.Matchers.is;
public class KibanaRoleTests extends ESTestCase { public class KibanaRoleTests extends ESTestCase {
public void testCluster() { public void testCluster() {
final User user = KibanaUser.INSTANCE;
final TransportRequest request = new TransportRequest.Empty(); final TransportRequest request = new TransportRequest.Empty();
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true)); final Authentication authentication = mock(Authentication.class);
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, user), is(true)); assertThat(KibanaRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, user), is(true)); assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(KibanaRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, user), is(true)); assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(KibanaRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(false)); assertThat(KibanaRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, user), is(false)); assertThat(KibanaRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(false)); assertThat(KibanaRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(KibanaRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true));
} }
public void testRunAs() { public void testRunAs() {

View File

@ -19,27 +19,27 @@ import org.elasticsearch.action.index.IndexAction;
import org.elasticsearch.action.search.MultiSearchAction; import org.elasticsearch.action.search.MultiSearchAction;
import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.marvel.action.MonitoringBulkAction; import org.elasticsearch.marvel.action.MonitoringBulkAction;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
import java.util.Arrays; import java.util.Arrays;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
public class KibanaUserRoleTests extends ESTestCase { public class KibanaUserRoleTests extends ESTestCase {
public void testCluster() { public void testCluster() {
final User user = new User("joe"); final Authentication authentication = mock(Authentication.class);
final TransportRequest request = new TransportRequest.Empty(); final TransportRequest request = new TransportRequest.Empty();
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true)); assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, user), is(true)); assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, user), is(true)); assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(KibanaUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(false)); assertThat(KibanaUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, user), is(false)); assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(false)); assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(KibanaUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(KibanaUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, user), is(false));
} }
public void testRunAs() { public void testRunAs() {

View File

@ -20,6 +20,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.shield.action.role.PutRoleAction; import org.elasticsearch.shield.action.role.PutRoleAction;
import org.elasticsearch.shield.action.user.PutUserAction; import org.elasticsearch.shield.action.user.PutUserAction;
import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; import org.elasticsearch.shield.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -28,6 +29,8 @@ import org.elasticsearch.transport.TransportRequest;
import java.util.Map; import java.util.Map;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/** /**
* Tests for the superuser role * Tests for the superuser role
@ -36,14 +39,16 @@ public class SuperuserRoleTests extends ESTestCase {
public void testCluster() { public void testCluster() {
final User user = new User("joe", SuperuserRole.NAME); final User user = new User("joe", SuperuserRole.NAME);
final Authentication authentication = mock(Authentication.class);
when(authentication.getUser()).thenReturn(user);
final TransportRequest request = new TransportRequest.Empty(); final TransportRequest request = new TransportRequest.Empty();
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true)); assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(true)); assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true));
assertThat(SuperuserRole.INSTANCE.cluster().check(PutUserAction.NAME, request, user), is(true)); assertThat(SuperuserRole.INSTANCE.cluster().check(PutUserAction.NAME, request, authentication), is(true));
assertThat(SuperuserRole.INSTANCE.cluster().check(PutRoleAction.NAME, request, user), is(true)); assertThat(SuperuserRole.INSTANCE.cluster().check(PutRoleAction.NAME, request, authentication), is(true));
assertThat(SuperuserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(true)); assertThat(SuperuserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(SuperuserRole.INSTANCE.cluster().check("internal:admin/foo", request, user), is(false)); assertThat(SuperuserRole.INSTANCE.cluster().check("internal:admin/foo", request, authentication), is(false));
} }
public void testIndices() { public void testIndices() {

View File

@ -12,7 +12,7 @@ import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestFilterChain; import org.elasticsearch.rest.RestFilterChain;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.SecurityLicenseState; import org.elasticsearch.shield.SecurityLicenseState;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -52,8 +52,8 @@ public class ShieldRestFilterTests extends ESTestCase {
public void testProcess() throws Exception { public void testProcess() throws Exception {
RestRequest request = mock(RestRequest.class); RestRequest request = mock(RestRequest.class);
User user = new User("_user", "r1"); Authentication authentication = mock(Authentication.class);
when(authcService.authenticate(request)).thenReturn(user); when(authcService.authenticate(request)).thenReturn(authentication);
filter.process(request, channel, chain); filter.process(request, channel, chain);
verify(chain).continueProcessing(request, channel); verify(chain).continueProcessing(request, channel);
verifyZeroInteractions(channel); verifyZeroInteractions(channel);

View File

@ -30,6 +30,6 @@ public class ClientTransportFilterTests extends ESTestCase {
public void testOutbound() throws Exception { public void testOutbound() throws Exception {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
filter.outbound("_action", request); filter.outbound("_action", request);
verify(authcService).attachUserHeaderIfMissing(SystemUser.INSTANCE); verify(authcService).attachUserIfMissing(SystemUser.INSTANCE);
} }
} }

View File

@ -8,7 +8,7 @@ package org.elasticsearch.shield.transport;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.authc.Authentication;
import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.action.ShieldActionMapper;
import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.authz.AuthorizationService;
@ -48,10 +48,10 @@ public class ServerTransportFilterTests extends ESTestCase {
public void testInbound() throws Exception { public void testInbound() throws Exception {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
User user = mock(User.class); Authentication authentication = mock(Authentication.class);
when(authcService.authenticate("_action", request, null)).thenReturn(user); when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
filter.inbound("_action", request, channel); filter.inbound("_action", request, channel);
verify(authzService).authorize(user, "_action", request); verify(authzService).authorize(authentication, "_action", request);
} }
public void testInboundAuthenticationException() throws Exception { public void testInboundAuthenticationException() throws Exception {
@ -68,9 +68,9 @@ public class ServerTransportFilterTests extends ESTestCase {
public void testInboundAuthorizationException() throws Exception { public void testInboundAuthorizationException() throws Exception {
TransportRequest request = mock(TransportRequest.class); TransportRequest request = mock(TransportRequest.class);
User user = mock(User.class); Authentication authentication = mock(Authentication.class);
when(authcService.authenticate("_action", request, null)).thenReturn(user); when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
doThrow(authorizationError("authz failed")).when(authzService).authorize(user, "_action", request); doThrow(authorizationError("authz failed")).when(authzService).authorize(authentication, "_action", request);
try { try {
filter.inbound("_action", request, channel); filter.inbound("_action", request, channel);
fail("expected filter inbound to throw an authorization exception on authorization error"); fail("expected filter inbound to throw an authorization exception on authorization error");