security: cleanup authentication service

This commit removes duplicated code in the authentication service by combining
the authentication logic for rest and transport requests. As part of this we no longer
cache the authentication token since we put the user in the context and serialize the
user.

Additionally we now pass the thread context to the AuthenticationFailureHandler to
restore access to the headers and context.

Original commit: elastic/x-pack-elasticsearch@79e2375a13
This commit is contained in:
jaymode 2016-04-07 21:40:46 -04:00
parent 4f7dad8da2
commit 91943318bf
12 changed files with 346 additions and 238 deletions

View File

@ -6,6 +6,7 @@
package org.elasticsearch.example.realm;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.DefaultAuthenticationFailureHandler;
@ -14,32 +15,34 @@ import org.elasticsearch.transport.TransportMessage;
public class CustomAuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
@Override
public ElasticsearchSecurityException unsuccessfulAuthentication(RestRequest request, AuthenticationToken token) {
ElasticsearchSecurityException e = super.unsuccessfulAuthentication(request, token);
public ElasticsearchSecurityException failedAuthentication(RestRequest request, AuthenticationToken token,
ThreadContext context) {
ElasticsearchSecurityException e = super.failedAuthentication(request, token, context);
// set a custom header
e.addHeader("WWW-Authenticate", "custom-challenge");
return e;
}
@Override
public ElasticsearchSecurityException unsuccessfulAuthentication(TransportMessage message, AuthenticationToken token, String action) {
ElasticsearchSecurityException e = super.unsuccessfulAuthentication(message, token, action);
public ElasticsearchSecurityException failedAuthentication(TransportMessage message, AuthenticationToken token, String action,
ThreadContext context) {
ElasticsearchSecurityException e = super.failedAuthentication(message, token, action, context);
// set a custom header
e.addHeader("WWW-Authenticate", "custom-challenge");
return e;
}
@Override
public ElasticsearchSecurityException missingToken(RestRequest request) {
ElasticsearchSecurityException e = super.missingToken(request);
public ElasticsearchSecurityException missingToken(RestRequest request, ThreadContext context) {
ElasticsearchSecurityException e = super.missingToken(request, context);
// set a custom header
e.addHeader("WWW-Authenticate", "custom-challenge");
return e;
}
@Override
public ElasticsearchSecurityException missingToken(TransportMessage message, String action) {
ElasticsearchSecurityException e = super.missingToken(message, action);
public ElasticsearchSecurityException missingToken(TransportMessage message, String action, ThreadContext context) {
ElasticsearchSecurityException e = super.missingToken(message, action, context);
// set a custom header
e.addHeader("WWW-Authenticate", "custom-challenge");
return e;

View File

@ -18,7 +18,7 @@ import java.net.InetAddress;
*/
public interface AuditTrail {
static final AuditTrail NOOP = new AuditTrail() {
AuditTrail NOOP = new AuditTrail() {
static final String NAME = "noop";
@ -67,6 +67,10 @@ public interface AuditTrail {
public void accessDenied(User user, String action, TransportMessage message) {
}
@Override
public void tamperedRequest(RestRequest request) {
}
@Override
public void tamperedRequest(String action, TransportMessage message) {
}
@ -114,6 +118,8 @@ public interface AuditTrail {
void accessDenied(User user, String action, TransportMessage message);
void tamperedRequest(RestRequest request);
void tamperedRequest(String action, TransportMessage message);
void tamperedRequest(User user, String action, TransportMessage request);

View File

@ -105,6 +105,13 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
}
}
@Override
public void tamperedRequest(RestRequest request) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.tamperedRequest(request);
}
}
@Override
public void tamperedRequest(String action, TransportMessage message) {
for (AuditTrail auditTrail : auditTrails) {

View File

@ -457,6 +457,17 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
}
}
@Override
public void tamperedRequest(RestRequest request) {
if (events.contains(TAMPERED_REQUEST)) {
try {
enqueue(message("tampered_request", null, null, null, null, request), "tampered_request");
} catch (Exception e) {
logger.warn("failed to index audit event: [tampered_request]", e);
}
}
}
@Override
public void tamperedRequest(String action, TransportMessage message) {
if (events.contains(TAMPERED_REQUEST)) {

View File

@ -290,6 +290,16 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent<LoggingAuditTr
}
}
@Override
public void tamperedRequest(RestRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("{}[rest] [tampered_request]\t{}, uri=[{}], request_body=[{}]", prefix, hostAttributes(request), request.uri(),
restRequestContent(request));
} else {
logger.error("{}[rest] [tampered_request]\t{}, uri=[{}]", prefix, hostAttributes(request), request.uri());
}
}
@Override
public void tamperedRequest(String action, TransportMessage message) {
String indices = indicesString(message);

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.transport.TransportMessage;
@ -32,11 +33,12 @@ public interface AuthenticationFailureHandler {
* This method is called when there has been an authentication failure for the given REST request and authentication
* token.
*
* @param request The request that could not be authenticated
* @param request The request that was being authenticated when the exception occurred
* @param token The token that was extracted from the request
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException unsuccessfulAuthentication(RestRequest request, AuthenticationToken token);
ElasticsearchSecurityException failedAuthentication(RestRequest request, AuthenticationToken token, ThreadContext context);
/**
* This method is called when there has been an authentication failure for the given message and token
@ -44,9 +46,11 @@ public interface AuthenticationFailureHandler {
* @param message The transport message that could not be authenticated
* @param token The token that was extracted from the message
* @param action The name of the action that the message is trying to perform
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException unsuccessfulAuthentication(TransportMessage message, AuthenticationToken token, String action);
ElasticsearchSecurityException failedAuthentication(TransportMessage message, AuthenticationToken token, String action,
ThreadContext context);
/**
* The method is called when an exception has occurred while processing the REST request. This could be an error that
@ -54,28 +58,32 @@ public interface AuthenticationFailureHandler {
*
* @param request The request that was being authenticated when the exception occurred
* @param e The exception that was thrown
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException exceptionProcessingRequest(RestRequest request, Exception e);
ElasticsearchSecurityException exceptionProcessingRequest(RestRequest request, Exception e, ThreadContext context);
/**
* The method is called when an exception has occurred while processing the transport message. This could be an error that
* occurred while attempting to extract a token or while attempting to authenticate the request
*
* @param message The message that was being authenticated when the exception occurred
* @param action The name of the action that the message is trying to perform
* @param e The exception that was thrown
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException exceptionProcessingRequest(TransportMessage message, Exception e);
ElasticsearchSecurityException exceptionProcessingRequest(TransportMessage message, String action, Exception e, ThreadContext context);
/**
* This method is called when a REST request is received and no authentication token could be extracted AND anonymous
* access is disabled. If anonymous access is enabled, this method will not be called
*
* @param request The request that did not have a token
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException missingToken(RestRequest request);
ElasticsearchSecurityException missingToken(RestRequest request, ThreadContext context);
/**
* This method is called when a transport message is received and no authentication token could be extracted AND
@ -83,17 +91,19 @@ public interface AuthenticationFailureHandler {
*
* @param message The message that did not have a token
* @param action The name of the action that the message is trying to perform
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException missingToken(TransportMessage message, String action);
ElasticsearchSecurityException missingToken(TransportMessage message, String action, ThreadContext context);
/**
* This method is called when anonymous access is enabled, a request does not pass authorization with the anonymous
* user, AND the anonymous service is configured to thrown a authentication exception instead of a authorization
* user, AND the anonymous service is configured to throw an authentication exception instead of an authorization
* exception
*
* @param action the action that failed authorization for anonymous access
* @param context The context of the request that failed authentication that could not be authenticated
* @return ElasticsearchSecurityException with the appropriate headers and message
*/
ElasticsearchSecurityException authenticationRequired(String action);
ElasticsearchSecurityException authenticationRequired(String action, ThreadContext context);
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.TransportMessage;
@ -19,17 +20,19 @@ import static org.elasticsearch.shield.support.Exceptions.authenticationError;
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public ElasticsearchSecurityException unsuccessfulAuthentication(RestRequest request, AuthenticationToken token) {
public ElasticsearchSecurityException failedAuthentication(RestRequest request, AuthenticationToken token,
ThreadContext context) {
return authenticationError("unable to authenticate user [{}] for REST request [{}]", token.principal(), request.uri());
}
@Override
public ElasticsearchSecurityException unsuccessfulAuthentication(TransportMessage message, AuthenticationToken token, String action) {
public ElasticsearchSecurityException failedAuthentication(TransportMessage message, AuthenticationToken token, String action,
ThreadContext context) {
return authenticationError("unable to authenticate user [{}] for action [{}]", token.principal(), action);
}
@Override
public ElasticsearchSecurityException exceptionProcessingRequest(RestRequest request, Exception e) {
public ElasticsearchSecurityException exceptionProcessingRequest(RestRequest request, Exception e, ThreadContext context) {
if (e instanceof ElasticsearchSecurityException) {
assert ((ElasticsearchSecurityException) e).status() == RestStatus.UNAUTHORIZED;
assert ((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate").size() == 1;
@ -39,7 +42,8 @@ public class DefaultAuthenticationFailureHandler implements AuthenticationFailur
}
@Override
public ElasticsearchSecurityException exceptionProcessingRequest(TransportMessage message, Exception e) {
public ElasticsearchSecurityException exceptionProcessingRequest(TransportMessage message, String action, Exception e,
ThreadContext context) {
if (e instanceof ElasticsearchSecurityException) {
assert ((ElasticsearchSecurityException) e).status() == RestStatus.UNAUTHORIZED;
assert ((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate").size() == 1;
@ -49,17 +53,17 @@ public class DefaultAuthenticationFailureHandler implements AuthenticationFailur
}
@Override
public ElasticsearchSecurityException missingToken(RestRequest request) {
public ElasticsearchSecurityException missingToken(RestRequest request, ThreadContext context) {
return authenticationError("missing authentication token for REST request [{}]", request.uri());
}
@Override
public ElasticsearchSecurityException missingToken(TransportMessage message, String action) {
public ElasticsearchSecurityException missingToken(TransportMessage message, String action, ThreadContext context) {
return authenticationError("missing authentication token for action [{}]", action);
}
@Override
public ElasticsearchSecurityException authenticationRequired(String action) {
public ElasticsearchSecurityException authenticationRequired(String action, ThreadContext context) {
return authenticationError("action [{}] requires authentication", action);
}
}

View File

@ -19,6 +19,7 @@ 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.rest.RestRequest;
import org.elasticsearch.shield.user.AnonymousUser;
import org.elasticsearch.shield.user.User;
@ -58,7 +59,7 @@ public class InternalAuthenticationService extends AbstractComponent implements
@Inject
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService,
AuthenticationFailureHandler failureHandler, ThreadPool threadPool) {
AuthenticationFailureHandler failureHandler, ThreadPool threadPool, RestController controller) {
super(settings);
this.realms = realms;
this.auditTrail = auditTrail;
@ -67,114 +68,38 @@ public class InternalAuthenticationService extends AbstractComponent implements
this.threadContext = threadPool.getThreadContext();
this.signUserHeader = SIGN_USER_HEADER.get(settings);
this.runAsEnabled = RUN_AS_ENABLED.get(settings);
if (runAsEnabled) {
controller.registerRelevantHeaders(RUN_AS_USER_HEADER);
}
}
@Override
public User authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException {
User user = getUserFromContext();
if (user != null) {
return user;
}
AuthenticationToken token;
try {
token = token();
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("failed to extract token from request", e);
} else {
logger.warn("failed to extract token from request: {}", e.getMessage());
}
auditTrail.authenticationFailed(request);
throw failureHandler.exceptionProcessingRequest(request, e);
}
if (token == null) {
if (AnonymousUser.enabled()) {
User anonymousUser = AnonymousUser.INSTANCE;
// we must put the user in the request context, so it'll be copied to the
// transport request - without it, the transport will assume system user
setUser(anonymousUser);
return anonymousUser;
}
auditTrail.anonymousAccessDenied(request);
throw failureHandler.missingToken(request);
}
try {
user = authenticate(request, token);
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("authentication of request failed for principal [{}], uri [{}]", e, token.principal(), request.uri());
}
auditTrail.authenticationFailed(token, request);
throw failureHandler.exceptionProcessingRequest(request, e);
}
if (user == null) {
throw failureHandler.unsuccessfulAuthentication(request, token);
}
if (runAsEnabled) {
String runAsUsername = request.header(RUN_AS_USER_HEADER);
if (runAsUsername != null) {
if (runAsUsername.isEmpty()) {
logger.warn("user [{}] attempted to runAs with an empty username", user.principal());
auditTrail.authenticationFailed(token, request);
throw failureHandler.unsuccessfulAuthentication(request, token);
}
User runAsUser;
try {
runAsUser = lookupUser(runAsUsername);
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("lookup of run as user failed for principal [{}], uri [{}], run as username [{}]", e,
token.principal(), request.uri(), runAsUsername);
}
auditTrail.authenticationFailed(token, request);
throw failureHandler.exceptionProcessingRequest(request, e);
}
// 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 [{}], uri [{}], run as username [{}]", e, token.principal(),
request.uri(), runAsUsername);
}
auditTrail.authenticationFailed(token, request);
throw failureHandler.exceptionProcessingRequest(request, e);
}
}
}
// we must put the user in the request context, so it'll be copied to the
// transport request - without it, the transport will assume system user
setUser(user);
return user;
return authenticate(newRequest(request), (User) null);
}
@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) {
auditTrail.tamperedRequest(action, message);
request.tamperedRequest();
throw e;
}
}
@ -182,10 +107,9 @@ public class InternalAuthenticationService extends AbstractComponent implements
assert user != null;
putUserInContext(user);
} else {
user = authenticateWithRealms(action, message, fallbackUser);
user = authenticateWithRealms(request, fallbackUser);
setUser(user);
}
return user;
}
@ -267,25 +191,23 @@ public class InternalAuthenticationService extends AbstractComponent implements
* <p>
* The order by which the realms are checked is defined in {@link Realms}.
*
* @param action The executed action
* @param message The executed request
* @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(String action, TransportMessage message, User fallbackUser) throws ElasticsearchSecurityException {
User authenticateWithRealms(AuditableRequest request, User fallbackUser) throws ElasticsearchSecurityException {
AuthenticationToken token;
try {
token = token(action, message);
token = token(request);
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("failed to extract token from transport message", e);
logger.debug("failed to extract token from request: [{}]", e, request);
} else {
logger.warn("failed to extract token from transport message: {}", e.getMessage());
logger.warn("failed to extract token from request: [{}]", request, e.getMessage());
}
auditTrail.authenticationFailed(action, message);
throw failureHandler.exceptionProcessingRequest(message, e);
throw request.exceptionProcessingRequest(e);
}
if (token == null) {
@ -295,23 +217,21 @@ public class InternalAuthenticationService extends AbstractComponent implements
if (AnonymousUser.enabled()) {
return AnonymousUser.INSTANCE;
}
auditTrail.anonymousAccessDenied(action, message);
throw failureHandler.missingToken(message, action);
throw request.anonymousAccessDenied();
}
User user;
try {
user = authenticate(message, token, action);
user = authenticate(request, token);
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("authentication of transport message failed for principal [{}], action [{}]", e, token.principal(), action);
logger.debug("authentication failed for principal [{}], [{}]", e, request);
}
auditTrail.authenticationFailed(token, action, message);
throw failureHandler.exceptionProcessingRequest(message, e);
throw request.exceptionProcessingRequest(e, token);
}
if (user == null) {
throw failureHandler.unsuccessfulAuthentication(message, token, action);
throw request.failedAuthentication(token);
}
if (runAsEnabled) {
@ -319,19 +239,17 @@ public class InternalAuthenticationService extends AbstractComponent implements
if (runAsUsername != null) {
if (runAsUsername.isEmpty()) {
logger.warn("user [{}] attempted to runAs with an empty username", user.principal());
auditTrail.authenticationFailed(token, action, message);
throw failureHandler.unsuccessfulAuthentication(message, token, action);
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 [{}], action [{}], run as username [{}]", e,
token.principal(), action, runAsUsername);
logger.debug("lookup of run as user failed for principal [{}], [{}], run as username [{}]", e,
token.principal(), request, runAsUsername);
}
auditTrail.authenticationFailed(token, action, message);
throw failureHandler.exceptionProcessingRequest(message, e);
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
@ -346,18 +264,17 @@ public class InternalAuthenticationService extends AbstractComponent implements
}
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("user creation failed for principal [{}], action [{}], run as username [{}]", e, token.principal(),
action, runAsUsername);
logger.debug("user creation failed for principal [{}], [{}], run as username [{}]", e, token.principal(),
request, runAsUsername);
}
auditTrail.authenticationFailed(token, action, message);
throw failureHandler.exceptionProcessingRequest(message, e);
throw request.exceptionProcessingRequest(e, token);
}
}
}
return user;
}
User authenticate(TransportMessage message, AuthenticationToken token, String action) throws ElasticsearchSecurityException {
User authenticate(AuditableRequest request, AuthenticationToken token) throws ElasticsearchSecurityException {
assert token != null : "cannot authenticate null tokens";
try {
for (Realm realm : realms) {
@ -366,63 +283,21 @@ public class InternalAuthenticationService extends AbstractComponent implements
if (user != null) {
return user;
}
auditTrail.authenticationFailed(realm.type(), token, action, message);
request.authenticationFailed(token, realm.name());
}
}
auditTrail.authenticationFailed(token, action, message);
request.authenticationFailed(token);
return null;
} finally {
token.clearCredentials();
}
}
User authenticate(RestRequest 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;
}
auditTrail.authenticationFailed(realm.type(), token, request);
}
}
auditTrail.authenticationFailed(token, request);
return null;
} finally {
token.clearCredentials();
}
}
AuthenticationToken token() throws ElasticsearchSecurityException {
assert threadContext.getTransient(TOKEN_KEY) == null;
AuthenticationToken token(AuditableRequest request) throws ElasticsearchSecurityException {
for (Realm realm : realms) {
AuthenticationToken token = realm.token(threadContext);
if (token != null) {
threadContext.putTransient(TOKEN_KEY, token);
return token;
}
}
return null;
}
@SuppressWarnings("unchecked")
AuthenticationToken token(String action, TransportMessage message) {
AuthenticationToken token = threadContext.getTransient(TOKEN_KEY);
if (token != null) {
return token;
}
for (Realm realm : realms) {
token = realm.token(threadContext);
if (token != null) {
if (logger.isTraceEnabled()) {
logger.trace("realm [{}] resolved authentication token [{}] from transport request with action [{}]", realm,
token.principal(), action);
}
threadContext.putTransient(TOKEN_KEY, token);
request.tokenResolved(realm.name(), token);
return token;
}
}
@ -445,4 +320,148 @@ public class InternalAuthenticationService extends AbstractComponent implements
settingsModule.registerSetting(SIGN_USER_HEADER);
settingsModule.registerSetting(RUN_AS_ENABLED);
}
// these methods are package private for testing. They are also needed so that a AuditableRequest can be created in tests
AuditableRequest newRequest(String action, TransportMessage message) {
return new Transport(action, message);
}
AuditableRequest newRequest(RestRequest request) {
return new Rest(request);
}
abstract class AuditableRequest {
abstract void authenticationFailed(AuthenticationToken token);
abstract void authenticationFailed(AuthenticationToken token, String realm);
abstract void tamperedRequest();
abstract void tokenResolved(String realm, AuthenticationToken token);
abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e);
abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token);
abstract ElasticsearchSecurityException failedAuthentication(AuthenticationToken token);
abstract ElasticsearchSecurityException anonymousAccessDenied();
}
class Transport extends AuditableRequest {
private final String action;
private final TransportMessage message;
Transport(String action, TransportMessage message) {
this.action = action;
this.message = message;
}
@Override
void authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, action, message);
}
@Override
void authenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, action, message);
}
@Override
void tamperedRequest() {
auditTrail.tamperedRequest(action, message);
}
@Override
void tokenResolved(String realm, AuthenticationToken token) {
logger.trace("realm [{}] resolved authentication token [{}] from transport request with action [{}]",
realm, token.principal(), action);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e) {
auditTrail.authenticationFailed(action, message);
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token) {
authenticationFailed(token);
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
}
ElasticsearchSecurityException failedAuthentication(AuthenticationToken token) {
auditTrail.authenticationFailed(token, action, message);
return failureHandler.failedAuthentication(message, token, action, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(action, message);
return failureHandler.missingToken(message, action, threadContext);
}
public String toString() {
return "transport action [" + action + "]";
}
}
class Rest extends AuditableRequest {
private final RestRequest request;
Rest(RestRequest request) {
this.request = request;
}
@Override
void authenticationFailed(AuthenticationToken token) {
auditTrail.authenticationFailed(token, request);
}
@Override
void authenticationFailed(AuthenticationToken token, String realm) {
auditTrail.authenticationFailed(realm, token, request);
}
@Override
void tamperedRequest() {
auditTrail.tamperedRequest(request);
}
@Override
void tokenResolved(String realm, AuthenticationToken token) {
logger.trace("realm [{}] resolved authentication token [{}] from rest request with uri [{}]",
realm, token.principal(), request.uri());
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e) {
auditTrail.authenticationFailed(request);
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
}
@Override
ElasticsearchSecurityException exceptionProcessingRequest(Exception e, AuthenticationToken token) {
authenticationFailed(token);
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
}
ElasticsearchSecurityException failedAuthentication(AuthenticationToken token) {
auditTrail.authenticationFailed(token, request);
return failureHandler.failedAuthentication(request, token, threadContext);
}
@Override
ElasticsearchSecurityException anonymousAccessDenied() {
auditTrail.anonymousAccessDenied(request);
return failureHandler.missingToken(request, threadContext);
}
public String toString() {
return "rest uri [" + request.uri() + "]";
}
}
}

View File

@ -347,7 +347,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
// Special case for anonymous user
if (AnonymousUser.enabled() && AnonymousUser.is(user)) {
if (anonymousAuthzExceptionEnabled == false) {
throw authcFailureHandler.authenticationRequired(action);
throw authcFailureHandler.authenticationRequired(action, threadContext);
}
}
if (user.runAs() != null) {

View File

@ -595,6 +595,22 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
}
}
public void testTamperedRequestRest() throws Exception {
initialize();
RestRequest request = mockRestRequest();
auditor.tamperedRequest(request);
awaitAuditDocumentCreation(resolveIndexName());
SearchHit hit = getIndexedAuditMessage();
assertAuditMessage(hit, "rest", "tampered_request");
Map<String, Object> sourceMap = hit.sourceAsMap();
assertThat(sourceMap.get("principal"), nullValue());
assertThat("127.0.0.1", equalTo(sourceMap.get("origin_address")));
assertThat("_uri", equalTo(sourceMap.get("uri")));
assertThat(sourceMap.get("origin_type"), is("rest"));
assertThat(sourceMap.get("request_body"), notNullValue());
}
public void testTamperedRequest() throws Exception {
initialize();
TransportRequest message = new RemoteHostMockTransportRequest();
@ -642,11 +658,21 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
public void testTamperedRequestMuted() throws Exception {
initialize("tampered_request");
TransportRequest message = new RemoteHostMockTransportRequest();
if (randomBoolean()) {
auditor.tamperedRequest(new User("_username", new String[]{"r1"}), "_action", message);
} else {
auditor.tamperedRequest("_action", message);
final int type = randomIntBetween(0, 2);
switch (type) {
case 0:
auditor.tamperedRequest(new User("_username", new String[]{"r1"}), "_action", message);
break;
case 1:
auditor.tamperedRequest("_action", message);
break;
case 2:
auditor.tamperedRequest(mockRestRequest());
break;
default:
throw new IllegalStateException("invalid value for type: " + type);
}
try {
getClient().prepareSearch(resolveIndexName()).setSize(0).setTerminateAfter(1).execute().actionGet();
fail("Expected IndexNotFoundException");

View File

@ -513,6 +513,33 @@ public class LoggingAuditTrailTests extends ESTestCase {
}
}
public void testTamperedRequestRest() throws Exception {
RestRequest request = mock(RestRequest.class);
InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1");
when(request.getRemoteAddress()).thenReturn(new InetSocketAddress(address, 9200));
when(request.uri()).thenReturn("_uri");
String expectedMessage = prepareRestContent(request);
for (Level level : Level.values()) {
threadContext = new ThreadContext(Settings.EMPTY);
CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext).start();
auditTrail.tamperedRequest(request);
switch (level) {
case ERROR:
case WARN:
case INFO:
assertMsg(logger, Level.ERROR, prefix + "[rest] [tampered_request]\torigin_address=[" +
NetworkAddress.format(address) + "], uri=[_uri]");
break;
case DEBUG:
case TRACE:
assertMsg(logger, Level.DEBUG, prefix + "[rest] [tampered_request]\torigin_address=[" +
NetworkAddress.format(address) + "], uri=[_uri], request_body=[" + expectedMessage + "]");
}
}
}
public void testTamperedRequest() throws Exception {
String action = "_action";
for (Level level : Level.values()) {

View File

@ -14,7 +14,9 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
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.user.AnonymousUser;
import org.elasticsearch.shield.user.SystemUser;
import org.elasticsearch.shield.user.User;
@ -46,6 +48,7 @@ import static org.hamcrest.Matchers.is;
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.Mockito.mock;
import static org.mockito.Mockito.never;
@ -76,6 +79,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
CryptoService cryptoService;
ThreadPool threadPool;
ThreadContext threadContext;
RestController controller;
@Before
public void init() throws Exception {
@ -83,9 +87,9 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
message = new InternalMessage();
restRequest = new FakeRestRequest();
firstRealm = mock(Realm.class);
when(firstRealm.type()).thenReturn("file");
when(firstRealm.name()).thenReturn("file");
secondRealm = mock(Realm.class);
when(secondRealm.type()).thenReturn("second");
when(secondRealm.name()).thenReturn("second");
Settings settings = Settings.builder().put("path.home", createTempDir()).build();
SecurityLicenseState shieldLicenseState = mock(SecurityLicenseState.class);
when(shieldLicenseState.customRealmsEnabled()).thenReturn(true);
@ -105,9 +109,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
auditTrail = mock(AuditTrail.class);
threadPool = mock(ThreadPool.class);
threadContext = new ThreadContext(Settings.EMPTY);
controller = mock(RestController.class);
when(threadPool.getThreadContext()).thenReturn(threadContext);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
}
@After
@ -120,31 +125,19 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(token);
AuthenticationToken result = service.token("_action", message);
AuthenticationToken result = service.token(service.newRequest("_action", message));
assertThat(result, notNullValue());
assertThat(result, is(token));
verifyZeroInteractions(auditTrail);
}
public void testTokenMissing() throws Exception {
AuthenticationToken token = service.token("_action", message);
AuthenticationToken token = service.token(service.newRequest("_action", message));
assertThat(token, nullValue());
verifyNoMoreInteractions(auditTrail);
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), nullValue());
}
public void testTokenCached() throws Exception {
threadContext.putTransient(InternalAuthenticationService.TOKEN_KEY, token);
AuthenticationToken result = service.token("_action", message);
assertThat(result, notNullValue());
assertThat(result, is(token));
verifyZeroInteractions(auditTrail);
verifyZeroInteractions(firstRealm);
verifyZeroInteractions(secondRealm);
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), notNullValue());
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), is((Object) token));
}
@SuppressWarnings("unchecked")
public void testAuthenticateBothSupportSecondSucceeds() throws Exception {
User user = new User("_username", "r1");
@ -154,7 +147,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(secondRealm.authenticate(token)).thenReturn(user);
service = spy(service);
doReturn(token).when(service).token("_action", message);
doReturn(token).when(service).token(any(AuditableRequest.class));
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
@ -175,7 +168,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(secondRealm.authenticate(token)).thenReturn(user);
service = spy(service);
doReturn(token).when(service).token("_action", message);
doReturn(token).when(service).token(any(AuditableRequest.class));
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
@ -216,19 +209,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
}
}
public void testTokenRestExists() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(token);
AuthenticationToken foundToken = service.token();
assertThat(foundToken, is(token));
assertThat(threadContext.getTransient(InternalAuthenticationService.TOKEN_KEY), equalTo((Object) token));
}
public void testTokenRestMissing() throws Exception {
when(firstRealm.token(threadContext)).thenReturn(null);
when(secondRealm.token(threadContext)).thenReturn(null);
AuthenticationToken token = service.token();
AuthenticationToken token = service.token(service.newRequest(restRequest));
assertThat(token, nullValue());
}
@ -250,7 +234,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(firstRealm.authenticate(token)).thenReturn(user);
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
service = spy(service);
doReturn(token).when(service).token("_action", message);
AuditableRequest request = service.newRequest("_action", message);
doReturn(token).when(service).token(request);
User result = service.authenticate("_action", message, null);
assertThat(result, notNullValue());
assertThat(result, is(user));
@ -352,7 +337,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putTransient(InternalAuthenticationService.USER_KEY,
threadContext.getTransient(InternalAuthenticationService.USER_KEY));
@ -366,7 +351,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
threadContext1 = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putHeader(InternalAuthenticationService.USER_KEY, threadContext.getHeader(InternalAuthenticationService.USER_KEY));
when(cryptoService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
@ -378,7 +363,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
assertThat(user, equalTo(user1));
verifyZeroInteractions(firstRealm);
@ -387,7 +372,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testAutheticateTransportContextAndHeaderNoSigning() throws Exception {
Settings settings = Settings.builder().put(InternalAuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
User user1 = new User("username", "r1", "r2");
when(firstRealm.supports(token)).thenReturn(true);
@ -406,7 +391,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
threadContext1.putTransient(InternalAuthenticationService.USER_KEY,
threadContext.getTransient(InternalAuthenticationService.USER_KEY));
User user = service.authenticate("_action", message1, SystemUser.INSTANCE);
@ -427,7 +412,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
when(threadPool.getThreadContext()).thenReturn(threadContext1);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
user = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
assertThat(user, equalTo(user1));
verifyZeroInteractions(firstRealm);
@ -486,7 +471,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
Settings settings = builder.build();
AnonymousUser.initialize(settings);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
threadPool);
threadPool, controller);
RestRequest request = new FakeRestRequest();
@ -505,7 +490,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
.build();
AnonymousUser.initialize(settings);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
InternalMessage message = new InternalMessage();
@ -521,7 +506,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
.build();
AnonymousUser.initialize(settings);
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService,
new DefaultAuthenticationFailureHandler(), threadPool);
new DefaultAuthenticationFailureHandler(), threadPool, controller);
InternalMessage message = new InternalMessage();
@ -626,7 +611,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testRealmLookupThrowingExceptionRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"));
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"}));
@ -665,7 +650,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testRunAsLookupSameRealmRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"));
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"}));
@ -708,7 +693,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testRunAsLookupDifferentRealmRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"));
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"}));
@ -729,7 +714,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
public void testRunAsWithEmptyRunAsUsernameRest() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, ""));
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"}));