mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-09 14:34:43 +00:00
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:
parent
27958cc708
commit
eeb964c886
@ -7,9 +7,10 @@ package org.elasticsearch.shield.authz;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
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.User;
|
||||
import org.elasticsearch.shield.authc.InternalAuthenticationService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
@ -33,21 +34,24 @@ public class AuthorizationUtilsTests extends ESTestCase {
|
||||
|
||||
public void testSystemUserSwitchWithNullorSystemUser() {
|
||||
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));
|
||||
}
|
||||
|
||||
public void testSystemUserSwitchWithNonSystemUser() {
|
||||
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"));
|
||||
assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true));
|
||||
}
|
||||
|
||||
public void testSystemUserSwitchWithNonSystemUserAndInternalAction() {
|
||||
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"));
|
||||
assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(false));
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public abstract class InternalClient extends FilterClient {
|
||||
|
||||
try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().stashContext()) {
|
||||
try {
|
||||
authcService.attachUserHeaderIfMissing(XPackUser.INSTANCE);
|
||||
authcService.attachUserIfMissing(XPackUser.INSTANCE);
|
||||
} catch (IOException ioe) {
|
||||
throw new ElasticsearchException("failed to attach internal user to request", ioe);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package org.elasticsearch.shield;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
@ -26,6 +27,12 @@ public interface SecurityContext {
|
||||
|
||||
User getUser();
|
||||
|
||||
Authentication getAuthentication();
|
||||
|
||||
default boolean hasAuthentication() {
|
||||
return getAuthentication() != null;
|
||||
}
|
||||
|
||||
class Insecure implements SecurityContext {
|
||||
|
||||
public static final Insecure INSTANCE = new Insecure();
|
||||
@ -51,6 +58,11 @@ public interface SecurityContext {
|
||||
public User getUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication getAuthentication() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Secure implements SecurityContext {
|
||||
@ -82,14 +94,20 @@ public interface SecurityContext {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
try {
|
||||
authcService.attachUserHeaderIfMissing(user);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to attach watcher user to request", e);
|
||||
authcService.attachUserIfMissing(user);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
throw new ElasticsearchException("failed to attach user to request", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,14 @@ import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.license.plugin.core.LicenseUtils;
|
||||
import org.elasticsearch.shield.Security;
|
||||
import org.elasticsearch.shield.SecurityContext;
|
||||
import org.elasticsearch.shield.action.ShieldActionMapper;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.user.SystemUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.shield.action.interceptor.RequestInterceptor;
|
||||
import org.elasticsearch.shield.audit.AuditTrail;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authc.InternalAuthenticationService;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.shield.authz.AuthorizationUtils;
|
||||
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 SecurityLicenseState licenseState;
|
||||
private final ThreadContext threadContext;
|
||||
private final SecurityContext securityContext;
|
||||
|
||||
@Inject
|
||||
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService,
|
||||
CryptoService cryptoService, AuditTrail auditTrail, SecurityLicenseState licenseState,
|
||||
ShieldActionMapper actionMapper, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool) {
|
||||
ShieldActionMapper actionMapper, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool,
|
||||
SecurityContext securityContext) {
|
||||
super(settings);
|
||||
this.authcService = authcService;
|
||||
this.authzService = authzService;
|
||||
@ -72,6 +75,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
|
||||
this.licenseState = licenseState;
|
||||
this.requestInterceptors = requestInterceptors;
|
||||
this.threadContext = threadPool.getThreadContext();
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
@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
|
||||
// and then a cleanup action is executed (like for search without a scroll)
|
||||
final ThreadContext.StoredContext original = threadContext.newStoredContext();
|
||||
final boolean restoreOriginalContext = threadContext.getHeader(InternalAuthenticationService.USER_KEY) != null ||
|
||||
threadContext.getTransient(InternalAuthenticationService.USER_KEY) != null;
|
||||
final boolean restoreOriginalContext = securityContext.hasAuthentication();
|
||||
try {
|
||||
if (licenseState.authenticationAndAuthorizationEnabled()) {
|
||||
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
|
||||
here if a request is not associated with any other user.
|
||||
*/
|
||||
String shieldAction = actionMapper.action(action, request);
|
||||
User user = authcService.authenticate(shieldAction, request, SystemUser.INSTANCE);
|
||||
authzService.authorize(user, shieldAction, request);
|
||||
final String shieldAction = actionMapper.action(action, request);
|
||||
Authentication authentication = authcService.authenticate(shieldAction, request, SystemUser.INSTANCE);
|
||||
assert authentication != null;
|
||||
authzService.authorize(authentication, shieldAction, request);
|
||||
final User user = authentication.getUser();
|
||||
request = unsign(user, shieldAction, request);
|
||||
|
||||
/*
|
||||
|
@ -94,6 +94,10 @@ public interface AuditTrail {
|
||||
@Override
|
||||
public void runAsDenied(User user, String action, TransportMessage message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsDenied(User user, RestRequest request) {
|
||||
}
|
||||
};
|
||||
|
||||
String name();
|
||||
@ -131,4 +135,6 @@ public interface AuditTrail {
|
||||
void runAsGranted(User user, String action, TransportMessage message);
|
||||
|
||||
void runAsDenied(User user, String action, TransportMessage message);
|
||||
|
||||
void runAsDenied(User user, RestRequest request);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +512,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
||||
public void runAsGranted(User user, String action, TransportMessage message) {
|
||||
if (events.contains(RUN_AS_GRANTED)) {
|
||||
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) {
|
||||
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) {
|
||||
if (events.contains(RUN_AS_DENIED)) {
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
ShieldIpFilterRule rule) throws IOException {
|
||||
|
||||
|
@ -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) {
|
||||
String formattedAddress;
|
||||
SocketAddress socketAddress = request.getRemoteAddress();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,12 @@ public interface AuthenticationService {
|
||||
* the user and that user is then "attached" to the request's context.
|
||||
*
|
||||
* @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
|
||||
* user credentials were found to be invalid
|
||||
* @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.
|
||||
@ -43,13 +43,13 @@ public interface AuthenticationService {
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* case where there was no user associated with the request, if the defautl
|
||||
* 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
|
||||
@ -57,7 +57,7 @@ public interface AuthenticationService {
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
@ -6,20 +6,18 @@
|
||||
package org.elasticsearch.shield.authc;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
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.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.shield.authc.Authentication.RealmRef;
|
||||
import org.elasticsearch.shield.user.AnonymousUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.shield.audit.AuditTrail;
|
||||
@ -28,11 +26,9 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
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}.
|
||||
@ -47,14 +43,12 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||
Setting.boolSetting(setting("authc.run_as.enabled"), true, Property.NodeScope);
|
||||
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 AuditTrail auditTrail;
|
||||
private final CryptoService cryptoService;
|
||||
private final AuthenticationFailureHandler failureHandler;
|
||||
private final ThreadContext threadContext;
|
||||
private final String nodeName;
|
||||
private final boolean signUserHeader;
|
||||
private final boolean runAsEnabled;
|
||||
|
||||
@ -62,6 +56,7 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService,
|
||||
AuthenticationFailureHandler failureHandler, ThreadPool threadPool, RestController controller) {
|
||||
super(settings);
|
||||
this.nodeName = Node.NODE_NAME_SETTING.get(settings);
|
||||
this.realms = realms;
|
||||
this.auditTrail = auditTrail;
|
||||
this.cryptoService = cryptoService;
|
||||
@ -75,394 +70,331 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException {
|
||||
return authenticate(newRequest(request), (User) null);
|
||||
public Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException {
|
||||
return createAuthenticator(request).authenticate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(String action, TransportMessage message, User fallbackUser) throws IOException {
|
||||
return authenticate(newRequest(action, message), fallbackUser);
|
||||
}
|
||||
|
||||
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;
|
||||
public Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException {
|
||||
return createAuthenticator(action, message, fallbackUser).authenticate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachUserHeaderIfMissing(User user) throws IOException {
|
||||
if (threadContext.getHeader(USER_KEY) != null) {
|
||||
return;
|
||||
}
|
||||
User transientUser = threadContext.getTransient(USER_KEY);
|
||||
if (transientUser != null) {
|
||||
setUserHeader(transientUser);
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(user);
|
||||
public void attachUserIfMissing(User user) throws IOException {
|
||||
Authentication authentication = new Authentication(user, new RealmRef("__attach", "__attach", nodeName), null);
|
||||
authentication.writeToContextIfMissing(threadContext, cryptoService, signUserHeader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getCurrentUser() {
|
||||
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) {
|
||||
public Authentication getCurrentAuthentication() {
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(text);
|
||||
StreamInput input = StreamInput.wrap(bytes);
|
||||
Version version = Version.readVersion(input);
|
||||
input.setVersion(version);
|
||||
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);
|
||||
}
|
||||
Authentication authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader);
|
||||
return authentication == null ? null : authentication;
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to read authentication", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the user associated with the given request by delegating the authentication to
|
||||
* 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}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The order by which the realms are checked is defined in {@link Realms}.
|
||||
*
|
||||
* @param request the request to authenticate
|
||||
* @param fallbackUser The user to assume if there is not other user attached to the message
|
||||
* @return The authenticated user
|
||||
* @throws ElasticsearchSecurityException If none of the configured realms successfully authenticated the
|
||||
* request
|
||||
*/
|
||||
User authenticateWithRealms(AuditableRequest request, User fallbackUser) throws ElasticsearchSecurityException {
|
||||
AuthenticationToken token;
|
||||
try {
|
||||
token = token(request);
|
||||
} 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);
|
||||
Authenticator createAuthenticator(RestRequest request) {
|
||||
return new Authenticator(request);
|
||||
}
|
||||
|
||||
Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser) {
|
||||
return new Authenticator(action, message, fallbackUser);
|
||||
}
|
||||
|
||||
class Authenticator {
|
||||
|
||||
private final AuditableRequest request;
|
||||
private final User fallbackUser;
|
||||
|
||||
private RealmRef authenticatedBy = null;
|
||||
private RealmRef lookedupBy = null;
|
||||
|
||||
Authenticator(RestRequest request) {
|
||||
this.request = new Rest(request);
|
||||
this.fallbackUser = null;
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
if (fallbackUser != null) {
|
||||
return fallbackUser;
|
||||
Authenticator(String action, TransportMessage message, User fallbackUser) {
|
||||
this.request = new Transport(action, message);
|
||||
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();
|
||||
}
|
||||
|
||||
User user;
|
||||
try {
|
||||
user = authenticate(request, token);
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
User authenticateToken(AuthenticationToken token) {
|
||||
User user = null;
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
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);
|
||||
throw request.exceptionProcessingRequest(e, token);
|
||||
} finally {
|
||||
token.clearCredentials();
|
||||
}
|
||||
throw request.exceptionProcessingRequest(e, token);
|
||||
return user;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
throw request.failedAuthentication(token);
|
||||
ElasticsearchSecurityException handleNullUser(AuthenticationToken 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);
|
||||
if (runAsUsername != null) {
|
||||
if (runAsUsername.isEmpty()) {
|
||||
logger.warn("user [{}] attempted to runAs with an empty username", user.principal());
|
||||
throw request.failedAuthentication(token);
|
||||
}
|
||||
User runAsUser;
|
||||
try {
|
||||
runAsUser = lookupUser(runAsUsername);
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("lookup of run as user failed for principal [{}], [{}], run as username [{}]", e,
|
||||
token.principal(), request, runAsUsername);
|
||||
if (runAsUsername == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runAsUsername.isEmpty()) {
|
||||
logger.debug("user [{}] attempted to runAs with an empty username", authenticatedUser.principal());
|
||||
throw request.runAsDenied(new User(authenticatedUser.principal(), authenticatedUser.roles(),
|
||||
new User(runAsUsername, Strings.EMPTY_ARRAY)), token);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
if (runAsUser != null) {
|
||||
user = new User(user.principal(), user.roles(), runAsUser);
|
||||
} else {
|
||||
// the requested run as user does not exist, but we don't throw an error here otherwise this could let
|
||||
// information leak about users in the system... instead we'll just let the authz service fail throw an
|
||||
// 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);
|
||||
}
|
||||
// the requested run as user does not exist, but we don't throw an error here otherwise this could let
|
||||
// information leak about users in the system... instead we'll just let the authz service fail throw an
|
||||
// authorization error
|
||||
user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY));
|
||||
} catch (Exception e) {
|
||||
logger.debug("run as 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 {
|
||||
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();
|
||||
}
|
||||
}
|
||||
abstract class AuditableRequest {
|
||||
|
||||
AuthenticationToken token(AuditableRequest request) throws ElasticsearchSecurityException {
|
||||
for (Realm realm : realms) {
|
||||
AuthenticationToken token = realm.token(threadContext);
|
||||
if (token != null) {
|
||||
request.tokenResolved(realm.name(), token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
||||
|
||||
User lookupUser(String username) {
|
||||
for (Realm realm : realms) {
|
||||
if (realm.userLookupSupported()) {
|
||||
User user = realm.lookupUser(username);
|
||||
if (user != null) {
|
||||
return user;
|
||||
abstract ElasticsearchSecurityException tamperedRequest();
|
||||
|
||||
abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token);
|
||||
|
||||
abstract ElasticsearchSecurityException authenticationFailed(AuthenticationToken token);
|
||||
|
||||
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) {
|
||||
settings.add(SIGN_USER_HEADER);
|
||||
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() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.elasticsearch.shield.authz;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
@ -29,11 +30,11 @@ public interface AuthorizationService {
|
||||
* have the appropriate privileges for this action/request, an {@link ElasticsearchSecurityException}
|
||||
* will be thrown.
|
||||
*
|
||||
* @param user The user
|
||||
* @param action The action
|
||||
* @param request The request
|
||||
* @param authentication The authentication information
|
||||
* @param action The action
|
||||
* @param request The 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;
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.elasticsearch.shield.authz;
|
||||
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.user.SystemUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.shield.authc.InternalAuthenticationService;
|
||||
@ -44,8 +45,8 @@ public final class AuthorizationUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
User user = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
if (user == null || SystemUser.is(user)) {
|
||||
Authentication authentication = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
|
||||
if (authentication == null || SystemUser.is(authentication.getUser())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,11 @@ import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.search.action.SearchTransportService;
|
||||
import org.elasticsearch.shield.ShieldTemplateService;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.user.AnonymousUser;
|
||||
import org.elasticsearch.shield.user.SystemUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
@ -140,32 +140,32 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
||||
}
|
||||
|
||||
@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
|
||||
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
|
||||
if (SystemUser.is(user)) {
|
||||
if (SystemUser.isAuthorized(action)) {
|
||||
if (SystemUser.is(authentication.getRunAsUser())) {
|
||||
if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) {
|
||||
setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL);
|
||||
grant(user, action, request);
|
||||
grant(authentication, action, request);
|
||||
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
|
||||
// is unknown
|
||||
if (permission == null || permission.isEmpty()) {
|
||||
if (isRunAs) {
|
||||
// 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 {
|
||||
throw denial(user, action, request);
|
||||
throw denial(authentication, action, request);
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,18 +173,17 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
||||
if (isRunAs) {
|
||||
// first we must authorize for the RUN_AS action
|
||||
RunAsPermission runAs = permission.runAs();
|
||||
if (runAs != null && runAs.check(user.runAs().principal())) {
|
||||
grantRunAs(user, action, request);
|
||||
permission = permission(user.runAs().roles());
|
||||
if (runAs != null && runAs.check(authentication.getRunAsUser().principal())) {
|
||||
grantRunAs(authentication, action, request);
|
||||
permission = permission(authentication.getRunAsUser().roles());
|
||||
|
||||
// permission can be null as it might be that the user's role
|
||||
// is unknown
|
||||
if (permission == null || permission.isEmpty()) {
|
||||
throw denial(user, action, request);
|
||||
throw denial(authentication, action, request);
|
||||
}
|
||||
effectiveUser = user.runAs();
|
||||
} 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)) {
|
||||
ClusterPermission cluster = permission.cluster();
|
||||
// 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);
|
||||
grant(user, action, request);
|
||||
grant(authentication, action, request);
|
||||
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
|
||||
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,
|
||||
@ -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
|
||||
//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.
|
||||
grant(user, action, request);
|
||||
grant(authentication, action, request);
|
||||
return;
|
||||
}
|
||||
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()) {
|
||||
throw denial(user, action, request);
|
||||
throw denial(authentication, action, request);
|
||||
}
|
||||
|
||||
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";
|
||||
MetaData metaData = clusterState.metaData();
|
||||
IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData);
|
||||
if (!indicesAccessControl.isGranted()) {
|
||||
throw denial(user, action, request);
|
||||
throw denial(authentication, action, request);
|
||||
} else if (indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME) != null
|
||||
&& indicesAccessControl.getIndexPermissions(ShieldTemplateService.SECURITY_INDEX_NAME).isGranted()
|
||||
&& XPackUser.is(user) == false
|
||||
&& XPackUser.is(authentication.getRunAsUser()) == 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
|
||||
// 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,
|
||||
ShieldTemplateService.SECURITY_INDEX_NAME);
|
||||
throw denial(user, action, request);
|
||||
// FIXME its not just the XPackUser. We said the elastic user and superusers could access this!
|
||||
logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]",
|
||||
authentication.getRunAsUser().principal(), action, ShieldTemplateService.SECURITY_INDEX_NAME);
|
||||
throw denial(authentication, action, request);
|
||||
} else {
|
||||
setIndicesAccessControl(indicesAccessControl);
|
||||
}
|
||||
@ -259,14 +259,14 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
||||
}
|
||||
indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData);
|
||||
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,
|
||||
// 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) {
|
||||
@ -304,15 +304,15 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
||||
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();
|
||||
for (IndicesAndAliasesResolver resolver : indicesAndAliasesResolvers) {
|
||||
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";
|
||||
throw denial(user, action, request);
|
||||
throw denial(authentication, action, request);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private ElasticsearchSecurityException denial(User user, String action, TransportRequest request) {
|
||||
auditTrail.accessDenied(user, action, request);
|
||||
return denialException(user, action);
|
||||
private ElasticsearchSecurityException denial(Authentication authentication, String action, TransportRequest request) {
|
||||
auditTrail.accessDenied(authentication.getUser(), action, request);
|
||||
return denialException(authentication, action);
|
||||
}
|
||||
|
||||
private ElasticsearchSecurityException denyRunAs(User user, String action, TransportRequest request) {
|
||||
auditTrail.runAsDenied(user, action, request);
|
||||
return denialException(user, action);
|
||||
private ElasticsearchSecurityException denyRunAs(Authentication authentication, String action, TransportRequest request) {
|
||||
auditTrail.runAsDenied(authentication.getUser(), action, request);
|
||||
return denialException(authentication, action);
|
||||
}
|
||||
|
||||
private void grant(User user, String action, TransportRequest request) {
|
||||
auditTrail.accessGranted(user, action, request);
|
||||
private void grant(Authentication authentication, String action, TransportRequest request) {
|
||||
auditTrail.accessGranted(authentication.getUser(), action, request);
|
||||
}
|
||||
|
||||
private void grantRunAs(User user, String action, TransportRequest request) {
|
||||
auditTrail.runAsGranted(user, action, request);
|
||||
private void grantRunAs(Authentication authentication, String action, TransportRequest 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
|
||||
if (AnonymousUser.enabled() && AnonymousUser.is(user)) {
|
||||
if (anonymousAuthzExceptionEnabled == false) {
|
||||
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(),
|
||||
user.runAs().principal());
|
||||
authentication.getRunAsUser().principal());
|
||||
}
|
||||
return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal());
|
||||
}
|
||||
|
@ -5,8 +5,8 @@
|
||||
*/
|
||||
package org.elasticsearch.shield.authz.permission;
|
||||
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.shield.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import java.util.List;
|
||||
@ -17,13 +17,13 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
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 final Core NONE = new Core(ClusterPrivilege.NONE) {
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, User user) {
|
||||
public boolean check(String action, TransportRequest request, Authentication authentication) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ public interface ClusterPermission extends Permission {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, User user) {
|
||||
public boolean check(String action, TransportRequest request, Authentication authentication) {
|
||||
return predicate.test(action);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ public interface ClusterPermission extends Permission {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, User user) {
|
||||
public boolean check(String action, TransportRequest request, Authentication authentication) {
|
||||
if (globals == null) {
|
||||
return false;
|
||||
}
|
||||
@ -73,7 +73,7 @@ public interface ClusterPermission extends Permission {
|
||||
if (global == null || global.cluster() == null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (global.cluster().check(action, request, user)) {
|
||||
if (global.cluster().check(action, request, authentication)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@
|
||||
*/
|
||||
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.ChangePasswordAction;
|
||||
import org.elasticsearch.shield.action.user.UserRequest;
|
||||
@ -40,18 +42,47 @@ public class DefaultRole extends Role {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, User user) {
|
||||
final boolean actionAllowed = super.check(action, request, user);
|
||||
public boolean check(String action, TransportRequest request, Authentication authentication) {
|
||||
final boolean actionAllowed = super.check(action, request, authentication);
|
||||
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;
|
||||
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];
|
||||
assert username != null;
|
||||
return user.principal().equals(username);
|
||||
final boolean sameUsername = authentication.getRunAsUser().principal().equals(username);
|
||||
if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
|
||||
return checkChangePasswordAction(authentication);
|
||||
}
|
||||
|
||||
assert AuthenticateAction.NAME.equals(action) || sameUsername == false;
|
||||
return sameUsername;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public interface ClientTransportFilter {
|
||||
the system user will be attached. There cannot be a request outgoing from this
|
||||
node that is not associated with a user.
|
||||
*/
|
||||
authcService.attachUserHeaderIfMissing(SystemUser.INSTANCE);
|
||||
authcService.attachUserIfMissing(SystemUser.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ package org.elasticsearch.shield.transport;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
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.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authc.pki.PkiRealm;
|
||||
@ -101,8 +101,8 @@ public interface ServerTransportFilter {
|
||||
}
|
||||
}
|
||||
|
||||
User user = authcService.authenticate(shieldAction, request, null);
|
||||
authzService.authorize(user, shieldAction, request);
|
||||
Authentication authentication = authcService.authenticate(shieldAction, request, null);
|
||||
authzService.authorize(authentication, shieldAction, request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,10 @@ import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.support.ActionFilterChain;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.shield.SecurityContext;
|
||||
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.User;
|
||||
import org.elasticsearch.shield.audit.AuditTrail;
|
||||
@ -62,7 +65,7 @@ public class ShieldActionFilterTests extends ESTestCase {
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
|
||||
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 {
|
||||
@ -71,10 +74,11 @@ public class ShieldActionFilterTests extends ESTestCase {
|
||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
Task task = mock(Task.class);
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
@ -85,8 +89,9 @@ public class ShieldActionFilterTests extends ESTestCase {
|
||||
RuntimeException exception = new RuntimeException("process-error");
|
||||
Task task = mock(Task.class);
|
||||
User user = new User("username", "r1", "r2");
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(user);
|
||||
doThrow(exception).when(authzService).authorize(user, "_action", request);
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doThrow(exception).when(authzService).authorize(authentication, "_action", request);
|
||||
filter.apply(task, "_action", request, listener, chain);
|
||||
verify(listener).onFailure(exception);
|
||||
verifyNoMoreInteractions(chain);
|
||||
@ -98,12 +103,13 @@ public class ShieldActionFilterTests extends ESTestCase {
|
||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
User user = mock(User.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.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
|
||||
filter.apply(task, "_action", request, listener, chain);
|
||||
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));
|
||||
}
|
||||
|
||||
@ -114,7 +120,8 @@ public class ShieldActionFilterTests extends ESTestCase {
|
||||
IllegalArgumentException sigException = new IllegalArgumentException("bad bad boy");
|
||||
User user = mock(User.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);
|
||||
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
|
||||
filter.apply(task, "_action", request, listener, chain);
|
||||
|
@ -7,7 +7,6 @@ package org.elasticsearch.shield.authc;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
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.rest.RestController;
|
||||
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.user.AnonymousUser;
|
||||
import org.elasticsearch.shield.user.SystemUser;
|
||||
@ -32,17 +32,14 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.shield.support.Exceptions.authenticationError;
|
||||
import static org.elasticsearch.test.ShieldTestsUtils.assertAuthenticationException;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
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.sameInstance;
|
||||
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.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
@ -66,9 +62,6 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
InternalAuthenticationService service;
|
||||
TransportMessage message;
|
||||
RestRequest restRequest;
|
||||
@ -88,10 +81,15 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
message = new InternalMessage();
|
||||
restRequest = new FakeRestRequest();
|
||||
firstRealm = mock(Realm.class);
|
||||
when(firstRealm.name()).thenReturn("file");
|
||||
when(firstRealm.type()).thenReturn("file");
|
||||
when(firstRealm.name()).thenReturn("file_realm");
|
||||
secondRealm = mock(Realm.class);
|
||||
when(secondRealm.name()).thenReturn("second");
|
||||
Settings settings = Settings.builder().put("path.home", createTempDir()).build();
|
||||
when(secondRealm.type()).thenReturn("second");
|
||||
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);
|
||||
when(shieldLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL);
|
||||
when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true);
|
||||
@ -113,7 +111,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
threadContext = new ThreadContext(Settings.EMPTY);
|
||||
controller = mock(RestController.class);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -127,17 +126,21 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
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, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
}
|
||||
|
||||
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());
|
||||
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, authenticator::handleNullToken);
|
||||
assertThat(e.getMessage(), containsString("missing authentication token"));
|
||||
verify(auditTrail).anonymousAccessDenied("_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), nullValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -147,20 +150,20 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.authenticate(token)).thenReturn(null); // first fails
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
if (randomBoolean()) {
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
} else {
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
}
|
||||
|
||||
service = spy(service);
|
||||
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);
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
verify(auditTrail).authenticationFailed("file", token, "_action", message);
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, notNullValue());
|
||||
assertThat(user1, sameInstance(user));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||
assertThat(result.getUser(), is(user));
|
||||
assertThat(result.getLookedUpBy(), is(nullValue()));
|
||||
assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
|
||||
verify(auditTrail).authenticationFailed(firstRealm.name(), token, "_action", message);
|
||||
verify(cryptoService).sign(any(String.class));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception {
|
||||
@ -168,36 +171,28 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.supports(token)).thenReturn(false);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
|
||||
service = spy(service);
|
||||
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);
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
assertThat(result.getUser(), is(user));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verify(firstRealm, never()).authenticate(token);
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, notNullValue());
|
||||
assertThat(user1, is((Object) user));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAuthenticateCached() throws Exception {
|
||||
User user = new User("_username", "r1");
|
||||
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user);
|
||||
User result = service.authenticate("_action", message, null);
|
||||
final Authentication authentication = new Authentication(new User("_username", "r1"), new RealmRef("test", "cached", "foo"), null);
|
||||
authentication.writeToContext(threadContext, cryptoService, true);
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
assertThat(result, is(authentication));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verifyZeroInteractions(firstRealm);
|
||||
verifyZeroInteractions(secondRealm);
|
||||
verifyZeroInteractions(cryptoService);
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, notNullValue());
|
||||
assertThat(user1, is(user));
|
||||
verify(cryptoService).sign(any(String.class));
|
||||
}
|
||||
|
||||
public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationException() throws Exception {
|
||||
@ -214,36 +209,30 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
public void testTokenRestMissing() throws Exception {
|
||||
when(firstRealm.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());
|
||||
}
|
||||
|
||||
public void testEncodeDecodeUser() 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 {
|
||||
public void authenticationInContextAndHeader() throws Exception {
|
||||
User user = new User("_username", "r1");
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||
service = spy(service);
|
||||
AuditableRequest request = service.newRequest("_action", message);
|
||||
doReturn(token).when(service).token(request);
|
||||
User result = service.authenticate("_action", message, null);
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
String userStr = threadContext.getHeader(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(result.getUser(), is(user));
|
||||
|
||||
String userStr = threadContext.getHeader(Authentication.AUTHENTICATION_KEY);
|
||||
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 {
|
||||
@ -276,38 +265,24 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
when(secondRealm.token(threadContext)).thenReturn(null);
|
||||
User user1 = new User("username", "r1", "r2");
|
||||
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||
User user2 = service.authenticate("_action", message, user1);
|
||||
assertThat(user1, sameInstance(user2));
|
||||
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user3, sameInstance(user2));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||
|
||||
Authentication result = service.authenticate("_action", message, user1);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAuthenticateTransportSuccessNoFallback() throws Exception {
|
||||
User user1 = new User("username", "r1", "r2");
|
||||
public void testAuthenticateTransportSuccess() throws Exception {
|
||||
User user = new User("username", "r1", "r2");
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
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"));
|
||||
}
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
public void testAuthenticateTransportSuccessWithFallback() throws Exception {
|
||||
User user1 = new User("username", "r1", "r2");
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
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"));
|
||||
Authentication result = service.authenticate("_action", message, fallback);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAuthenticateRestSuccess() throws Exception {
|
||||
@ -315,10 +290,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
User user2 = service.authenticate(restRequest);
|
||||
assertThat(user1, sameInstance(user2));
|
||||
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user3, sameInstance(user2));
|
||||
Authentication result = service.authenticate(restRequest);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAutheticateTransportContextAndHeader() throws Exception {
|
||||
@ -326,12 +301,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
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(user2));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||
Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(authentication);
|
||||
reset(firstRealm);
|
||||
|
||||
// checking authentication from the context
|
||||
@ -341,10 +314,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
|
||||
threadContext1.putTransient(InternalAuthenticationService.USER_KEY,
|
||||
threadContext.getTransient(InternalAuthenticationService.USER_KEY));
|
||||
User user = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(user, sameInstance(user1));
|
||||
threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(ctxAuth, sameInstance(authentication));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
reset(firstRealm);
|
||||
|
||||
@ -354,8 +327,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
threadContext1.putHeader(InternalAuthenticationService.USER_KEY, threadContext.getHeader(InternalAuthenticationService.USER_KEY));
|
||||
when(cryptoService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode());
|
||||
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
threadContext1.writeTo(output);
|
||||
@ -366,12 +339,13 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(user, equalTo(user1));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
}
|
||||
|
||||
public void testAutheticateTransportContextAndHeaderNoSigning() throws Exception {
|
||||
public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception {
|
||||
Settings settings = Settings.builder().put(InternalAuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
|
||||
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
@ -380,12 +354,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
User user2 = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(user1, sameInstance(user2));
|
||||
User user3 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user3, sameInstance(user2));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY),
|
||||
equalTo((Object) InternalAuthenticationService.encodeUser(user1, null)));
|
||||
Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(authentication, false);
|
||||
reset(firstRealm);
|
||||
|
||||
// checking authentication from the context
|
||||
@ -394,17 +366,16 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
threadContext1.putTransient(InternalAuthenticationService.USER_KEY,
|
||||
threadContext.getTransient(InternalAuthenticationService.USER_KEY));
|
||||
User user = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(user, sameInstance(user1));
|
||||
threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(ctxAuth, sameInstance(authentication));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
reset(firstRealm);
|
||||
|
||||
|
||||
// checking authentication from the user header
|
||||
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();
|
||||
threadContext1.writeTo(output);
|
||||
@ -415,8 +386,9 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(user, equalTo(user1));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
|
||||
verifyZeroInteractions(cryptoService);
|
||||
@ -424,8 +396,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
|
||||
public void testAuthenticateTamperedUser() throws Exception {
|
||||
InternalMessage message = new InternalMessage();
|
||||
threadContext.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user");
|
||||
when(cryptoService.unsignAndVerify("_signed_user")).thenThrow(
|
||||
threadContext.putHeader(Authentication.AUTHENTICATION_KEY, "_signed_auth");
|
||||
when(cryptoService.unsignAndVerify("_signed_auth")).thenThrow(
|
||||
randomFrom(new RuntimeException(), new IllegalArgumentException(), new IllegalStateException()));
|
||||
|
||||
try {
|
||||
@ -444,23 +416,26 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
} else {
|
||||
user = new User("username", "r1", "r2");
|
||||
}
|
||||
assertThat(threadContext.getTransient(InternalAuthenticationService.USER_KEY), nullValue());
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||
service.attachUserHeaderIfMissing(user);
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, sameInstance((Object) user));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||
assertThat(threadContext.getTransient(Authentication.AUTHENTICATION_KEY), nullValue());
|
||||
assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), nullValue());
|
||||
service.attachUserIfMissing(user);
|
||||
|
||||
Authentication authentication = threadContext.getTransient(Authentication.AUTHENTICATION_KEY);
|
||||
assertThat(authentication, notNullValue());
|
||||
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 {
|
||||
User user = new User("username", "r1", "r2");
|
||||
threadContext.putTransient(InternalAuthenticationService.USER_KEY, user);
|
||||
threadContext.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user");
|
||||
service.attachUserHeaderIfMissing(new User("username2", "r3", "r4"));
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, sameInstance(user));
|
||||
assertThat(threadContext.getHeader(InternalAuthenticationService.USER_KEY), equalTo("_signed_user"));
|
||||
Authentication authentication = new Authentication(new User("username", "r1", "r2"), new RealmRef("test", "test", "foo"), null);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
threadContext.putHeader(Authentication.AUTHENTICATION_KEY, "_signed_auth");
|
||||
service.attachUserIfMissing(new User("username2", "r3", "r4"));
|
||||
assertThreadContextContainsAuthentication(authentication);
|
||||
}
|
||||
|
||||
public void testAnonymousUserRest() throws Exception {
|
||||
@ -474,16 +449,13 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
AnonymousUser.initialize(settings);
|
||||
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
|
||||
threadPool, controller);
|
||||
|
||||
RestRequest request = new FakeRestRequest();
|
||||
|
||||
User user = service.authenticate(request);
|
||||
User user1 = threadContext.getTransient(InternalAuthenticationService.USER_KEY);
|
||||
assertThat(user1, notNullValue());
|
||||
assertThat(user1, sameInstance((Object) user));
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.principal(), equalTo(username));
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||
Authentication result = service.authenticate(request);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance((Object) AnonymousUser.INSTANCE));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAnonymousUserTransportNoDefaultUser() throws Exception {
|
||||
@ -493,13 +465,12 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
AnonymousUser.initialize(settings);
|
||||
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, controller);
|
||||
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
User user = service.authenticate("_action", message, null);
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.principal(), equalTo(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME));
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(AnonymousUser.INSTANCE));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAnonymousUserTransportWithDefaultUser() throws Exception {
|
||||
@ -512,9 +483,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
User user = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user, sameInstance(SystemUser.INSTANCE));
|
||||
Authentication result = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(SystemUser.INSTANCE));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
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.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(authenticated.runAs(), is(notNullValue()));
|
||||
@ -646,29 +625,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
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 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));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
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.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(authenticated.runAs(), is(notNullValue()));
|
||||
@ -689,74 +653,60 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
||||
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 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));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testRunAsWithEmptyRunAsUsernameRest() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User("lookup user", new String[]{"user"});
|
||||
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "");
|
||||
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.authenticate(token)).thenReturn(user);
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verify(auditTrail).runAsDenied(any(User.class), eq(restRequest));
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
public void testRunAsWithEmptyRunAsUsername() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User("lookup user", new String[]{"user"});
|
||||
threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "");
|
||||
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.authenticate(token)).thenReturn(user);
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verify(auditTrail).runAsDenied(any(User.class), eq("_action"), eq(message));
|
||||
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 {
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.search.action.SearchTransportService;
|
||||
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.SystemUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
@ -123,10 +125,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
|
||||
// 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);
|
||||
|
||||
internalAuthorizationService.authorize(SystemUser.INSTANCE, "internal:whatever", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "internal:whatever", request);
|
||||
verify(auditTrail).accessGranted(SystemUser.INSTANCE, "internal:whatever", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
@ -134,7 +136,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
public void testIndicesActionsAreNotAuthorized() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
try {
|
||||
internalAuthorizationService.authorize(SystemUser.INSTANCE, "indices:", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:", request);
|
||||
fail("action beginning with indices should have failed");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e,
|
||||
@ -147,7 +149,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
public void testClusterAdminActionsAreNotAuthorized() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e,
|
||||
@ -160,7 +162,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
public void testClusterAdminSnapshotStatusActionIsNotAuthorized() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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();
|
||||
User user = new User("test user");
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
|
||||
fail("user without roles should be denied");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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();
|
||||
User user = new User("test user", "non-existent-role");
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
|
||||
fail("user with unknown role only should have been denied");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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());
|
||||
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "whatever", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "whatever", request);
|
||||
fail("non indices and non cluster requests should be denied");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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());
|
||||
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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());
|
||||
|
||||
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
|
||||
internalAuthorizationService.authorize(user, ClearScrollAction.NAME, clearScrollRequest);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), ClearScrollAction.NAME, clearScrollRequest);
|
||||
verify(auditTrail).accessGranted(user, ClearScrollAction.NAME, clearScrollRequest);
|
||||
|
||||
SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
|
||||
internalAuthorizationService.authorize(user, SearchScrollAction.NAME, searchScrollRequest);
|
||||
internalAuthorizationService.authorize(createAuthentication(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
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
@ -266,7 +269,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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);
|
||||
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e,
|
||||
@ -308,7 +311,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
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);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
@ -364,7 +367,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e,
|
||||
@ -395,7 +398,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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" }));
|
||||
assertThat(user.runAs(), is(notNullValue()));
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
|
||||
fail("user without roles should be denied for run as");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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());
|
||||
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
|
||||
fail("user without roles should be denied for run as");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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 {
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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")
|
||||
.build());
|
||||
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request);
|
||||
verify(auditTrail).runAsGranted(user, "indices:a", request);
|
||||
verify(auditTrail).accessGranted(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
@ -533,7 +536,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
String action = requestTuple.v1();
|
||||
TransportRequest request = requestTuple.v2();
|
||||
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");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
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
|
||||
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);
|
||||
|
||||
// multiple indices
|
||||
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);
|
||||
}
|
||||
|
||||
@ -580,7 +583,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
for (Tuple<String, ? extends TransportRequest> requestTuple : requests) {
|
||||
String action = requestTuple.v1();
|
||||
TransportRequest request = requestTuple.v2();
|
||||
internalAuthorizationService.authorize(user, action, request);
|
||||
internalAuthorizationService.authorize(createAuthentication(user), action, request);
|
||||
verify(auditTrail).accessGranted(user, action, request);
|
||||
}
|
||||
}
|
||||
@ -612,8 +615,13 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
||||
for (Tuple<String, TransportRequest> requestTuple : requests) {
|
||||
String action = requestTuple.v1();
|
||||
TransportRequest request = requestTuple.v2();
|
||||
internalAuthorizationService.authorize(XPackUser.INSTANCE, action, request);
|
||||
internalAuthorizationService.authorize(createAuthentication(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);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,14 @@ import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
|
||||
import org.elasticsearch.shield.action.user.AuthenticateRequestBuilder;
|
||||
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.action.user.AuthenticateAction;
|
||||
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.is;
|
||||
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.when;
|
||||
|
||||
/**
|
||||
* 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 AuthenticateRequestBuilder(mock(Client.class)).username(user.principal()).request();
|
||||
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() {
|
||||
@ -64,19 +83,33 @@ public class DefaultRoleTests extends ESTestCase {
|
||||
new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() :
|
||||
new AuthenticateRequestBuilder(mock(Client.class)).username(username).request();
|
||||
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);
|
||||
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) {
|
||||
((ChangePasswordRequest)request).username("joe");
|
||||
} else {
|
||||
((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, user2), is(false));
|
||||
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, user), is(true));
|
||||
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true));
|
||||
}
|
||||
|
||||
public void testDefaultRoleDoesNotAllowOtherActions() {
|
||||
@ -84,8 +117,85 @@ public class DefaultRoleTests extends ESTestCase {
|
||||
final TransportRequest request = mock(TransportRequest.class);
|
||||
final String action = randomFrom(PutUserAction.NAME, DeleteUserAction.NAME, ClusterHealthAction.NAME, ClusterStateAction.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));
|
||||
verifyZeroInteractions(user, request);
|
||||
assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,14 @@ import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateActio
|
||||
import org.elasticsearch.action.delete.DeleteAction;
|
||||
import org.elasticsearch.action.index.IndexAction;
|
||||
import org.elasticsearch.marvel.action.MonitoringBulkAction;
|
||||
import org.elasticsearch.shield.user.KibanaUser;
|
||||
import org.elasticsearch.shield.user.User;
|
||||
import org.elasticsearch.shield.authc.Authentication;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for the kibana role
|
||||
@ -32,15 +32,15 @@ import static org.hamcrest.Matchers.is;
|
||||
public class KibanaRoleTests extends ESTestCase {
|
||||
|
||||
public void testCluster() {
|
||||
final User user = KibanaUser.INSTANCE;
|
||||
final TransportRequest request = new TransportRequest.Empty();
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(false));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, user), is(false));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(false));
|
||||
final Authentication authentication = mock(Authentication.class);
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
|
||||
assertThat(KibanaRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), 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() {
|
||||
|
@ -19,27 +19,27 @@ import org.elasticsearch.action.index.IndexAction;
|
||||
import org.elasticsearch.action.search.MultiSearchAction;
|
||||
import org.elasticsearch.action.search.SearchAction;
|
||||
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.transport.TransportRequest;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class KibanaUserRoleTests extends ESTestCase {
|
||||
|
||||
public void testCluster() {
|
||||
final User user = new User("joe");
|
||||
final Authentication authentication = mock(Authentication.class);
|
||||
final TransportRequest request = new TransportRequest.Empty();
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, user), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, user), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(false));
|
||||
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, user), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
|
||||
assertThat(KibanaUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
|
||||
}
|
||||
|
||||
public void testRunAs() {
|
||||
|
@ -20,6 +20,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.shield.action.role.PutRoleAction;
|
||||
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.user.User;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
@ -28,6 +29,8 @@ import org.elasticsearch.transport.TransportRequest;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for the superuser role
|
||||
@ -36,14 +39,16 @@ public class SuperuserRoleTests extends ESTestCase {
|
||||
|
||||
public void testCluster() {
|
||||
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();
|
||||
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, user), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, user), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutUserAction.NAME, request, user), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutRoleAction.NAME, request, user), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, user), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check("internal:admin/foo", request, user), is(false));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutUserAction.NAME, request, authentication), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutRoleAction.NAME, request, authentication), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
|
||||
assertThat(SuperuserRole.INSTANCE.cluster().check("internal:admin/foo", request, authentication), is(false));
|
||||
}
|
||||
|
||||
public void testIndices() {
|
||||
|
@ -12,7 +12,7 @@ import org.elasticsearch.rest.RestChannel;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestFilterChain;
|
||||
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.SecurityLicenseState;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
@ -52,8 +52,8 @@ public class ShieldRestFilterTests extends ESTestCase {
|
||||
|
||||
public void testProcess() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
User user = new User("_user", "r1");
|
||||
when(authcService.authenticate(request)).thenReturn(user);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authcService.authenticate(request)).thenReturn(authentication);
|
||||
filter.process(request, channel, chain);
|
||||
verify(chain).continueProcessing(request, channel);
|
||||
verifyZeroInteractions(channel);
|
||||
|
@ -30,6 +30,6 @@ public class ClientTransportFilterTests extends ESTestCase {
|
||||
public void testOutbound() throws Exception {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
filter.outbound("_action", request);
|
||||
verify(authcService).attachUserHeaderIfMissing(SystemUser.INSTANCE);
|
||||
verify(authcService).attachUserIfMissing(SystemUser.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ package org.elasticsearch.shield.transport;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
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.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
@ -48,10 +48,10 @@ public class ServerTransportFilterTests extends ESTestCase {
|
||||
|
||||
public void testInbound() throws Exception {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = mock(User.class);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
|
||||
filter.inbound("_action", request, channel);
|
||||
verify(authzService).authorize(user, "_action", request);
|
||||
verify(authzService).authorize(authentication, "_action", request);
|
||||
}
|
||||
|
||||
public void testInboundAuthenticationException() throws Exception {
|
||||
@ -68,9 +68,9 @@ public class ServerTransportFilterTests extends ESTestCase {
|
||||
|
||||
public void testInboundAuthorizationException() throws Exception {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = mock(User.class);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||
doThrow(authorizationError("authz failed")).when(authzService).authorize(user, "_action", request);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
|
||||
doThrow(authorizationError("authz failed")).when(authzService).authorize(authentication, "_action", request);
|
||||
try {
|
||||
filter.inbound("_action", request, channel);
|
||||
fail("expected filter inbound to throw an authorization exception on authorization error");
|
||||
|
Loading…
x
Reference in New Issue
Block a user