migrate authentication service to an async model
This commit migrates the authentication service to an asynchronous model where we use listeners instead of blocking and waiting for the authentication to return. This is the first part of making authentication asynchronous as we still have blocking I/O inside of realms. See elastic/elasticsearch#3790 Original commit: elastic/x-pack-elasticsearch@9339af4af8
This commit is contained in:
parent
68c026d273
commit
31c851f5c2
|
@ -100,55 +100,43 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
throw LicenseUtils.newComplianceException(XPackPlugin.SECURITY);
|
||||
}
|
||||
|
||||
if (licenseState.isAuthAllowed() == false) {
|
||||
if (SECURITY_ACTION_MATCHER.test(action)) {
|
||||
// TODO we should be nice and just call the listener
|
||||
listener.onFailure(LicenseUtils.newComplianceException(XPackPlugin.SECURITY));
|
||||
} else {
|
||||
chain.proceed(task, action, request, listener);
|
||||
if (licenseState.isAuthAllowed()) {
|
||||
// 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 boolean restoreOriginalContext = securityContext.getAuthentication() != null;
|
||||
final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action);
|
||||
// we should always restore the original here because we forcefully changed to the system user
|
||||
final ThreadContext.StoredContext toRestore =
|
||||
restoreOriginalContext || useSystemUser ? threadContext.newStoredContext() : () -> {};
|
||||
final ActionListener<ActionResponse> signingListener = new ContextPreservingActionListener<>(threadContext, toRestore,
|
||||
ActionListener.wrap(r -> {
|
||||
try {
|
||||
listener.onResponse(sign(r));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
ActionListener<Void> authenticatedListener =
|
||||
ActionListener.wrap((aVoid) -> chain.proceed(task, action, request, signingListener), signingListener::onFailure);
|
||||
try {
|
||||
if (useSystemUser) {
|
||||
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> {
|
||||
try {
|
||||
applyInternal(action, request, authenticatedListener);
|
||||
} catch (IOException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
applyInternal(action, request, authenticatedListener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 originalContext = threadContext.newStoredContext();
|
||||
final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action);
|
||||
// we should always restore the original here because we forcefully changed to the system user
|
||||
final ThreadContext.StoredContext toRestore = useSystemUser ? originalContext : () -> {};
|
||||
final ActionListener<ActionResponse> signingListener =
|
||||
new ContextPreservingActionListener<>(threadContext, toRestore, ActionListener.wrap(r -> {
|
||||
try {
|
||||
listener.onResponse(sign(r));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
ActionListener<Void> authenticatedListener = new ContextPreservingActionListener<>(threadContext, toRestore,
|
||||
new ActionListener<Void>() {
|
||||
@Override
|
||||
public void onResponse(Void aVoid) {
|
||||
chain.proceed(task, action, request, signingListener);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
signingListener.onFailure(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
if (useSystemUser) {
|
||||
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> {
|
||||
try {
|
||||
applyInternal(action, request, authenticatedListener);
|
||||
} catch (IOException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
applyInternal(action, request, authenticatedListener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
} else if (SECURITY_ACTION_MATCHER.test(action)) {
|
||||
listener.onFailure(LicenseUtils.newComplianceException(XPackPlugin.SECURITY));
|
||||
} else {
|
||||
chain.proceed(task, action, request, listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +150,7 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
private void applyInternal(String action, final ActionRequest request, ActionListener listener) throws IOException {
|
||||
private void applyInternal(String action, final ActionRequest request, ActionListener<Void> listener) throws IOException {
|
||||
if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) {
|
||||
IndicesRequest indicesRequest = (IndicesRequest) request;
|
||||
try {
|
||||
|
@ -171,6 +159,7 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
here we fallback on the system user. Internal system requests are requests that are triggered by
|
||||
the system itself (e.g. pings, update mappings, share relocation, etc...) and were not originated
|
||||
|
@ -182,27 +171,33 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
here if a request is not associated with any other user.
|
||||
*/
|
||||
final String securityAction = actionMapper.action(action, request);
|
||||
Authentication authentication = authcService.authenticate(securityAction, request, SystemUser.INSTANCE);
|
||||
assert authentication != null;
|
||||
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer = new AuthorizationUtils.AsyncAuthorizer(authentication, listener,
|
||||
(userRoles, runAsRoles) -> {
|
||||
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
|
||||
final User user = authentication.getUser();
|
||||
unsign(user, securityAction, request);
|
||||
authcService.authenticate(securityAction, request, SystemUser.INSTANCE,
|
||||
ActionListener.wrap((authc) -> authorizeRequest(authc, securityAction, request, listener), listener::onFailure));
|
||||
}
|
||||
|
||||
/*
|
||||
* We use a separate concept for code that needs to be run after authentication and authorization that could effect the
|
||||
* running of the action. This is done to make it more clear of the state of the request.
|
||||
*/
|
||||
for (RequestInterceptor interceptor : requestInterceptors) {
|
||||
if (interceptor.supports(request)) {
|
||||
interceptor.intercept(request, user);
|
||||
void authorizeRequest(Authentication authentication, String securityAction, ActionRequest request, ActionListener listener) {
|
||||
if (authentication == null) {
|
||||
listener.onFailure(new IllegalArgumentException("authentication must be non null for authorization"));
|
||||
} else {
|
||||
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer = new AuthorizationUtils.AsyncAuthorizer(authentication, listener,
|
||||
(userRoles, runAsRoles) -> {
|
||||
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
|
||||
final User user = authentication.getUser();
|
||||
ActionRequest unsignedRequest = unsign(user, securityAction, request);
|
||||
|
||||
/*
|
||||
* We use a separate concept for code that needs to be run after authentication and authorization that could
|
||||
* affect the running of the action. This is done to make it more clear of the state of the request.
|
||||
*/
|
||||
for (RequestInterceptor interceptor : requestInterceptors) {
|
||||
if (interceptor.supports(unsignedRequest)) {
|
||||
interceptor.intercept(unsignedRequest, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.onResponse(null);
|
||||
});
|
||||
asyncAuthorizer.authorize(authzService);
|
||||
|
||||
listener.onResponse(null);
|
||||
});
|
||||
asyncAuthorizer.authorize(authzService);
|
||||
}
|
||||
}
|
||||
|
||||
ActionRequest unsign(User user, String action, final ActionRequest request) {
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authc;
|
|||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
|
@ -28,6 +29,7 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.elasticsearch.xpack.security.Security.setting;
|
||||
|
||||
|
@ -76,13 +78,9 @@ public class AuthenticationService extends AbstractComponent {
|
|||
* the user and that user is then "attached" to the request's context.
|
||||
*
|
||||
* @param request The request to be authenticated
|
||||
* @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
|
||||
*/
|
||||
public Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException {
|
||||
return createAuthenticator(request).authenticate();
|
||||
public void authenticate(RestRequest request, ActionListener<Authentication> authenticationListener) {
|
||||
createAuthenticator(request, authenticationListener).authenticateAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,15 +95,9 @@ public class AuthenticationService extends AbstractComponent {
|
|||
* {@code null}, in which case there will be no fallback user and the success/failure of the
|
||||
* 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 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.
|
||||
*/
|
||||
public Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException {
|
||||
return createAuthenticator(action, message, fallbackUser).authenticate();
|
||||
public void authenticate(String action, TransportMessage message, User fallbackUser, ActionListener<Authentication> listener) {
|
||||
createAuthenticator(action, message, fallbackUser, listener).authenticateAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,340 +106,463 @@ public class AuthenticationService extends AbstractComponent {
|
|||
*
|
||||
* @param user The user to be attached if the header is missing
|
||||
*/
|
||||
public void attachUserIfMissing(User user) throws IOException {
|
||||
void attachUserIfMissing(User user) throws IOException {
|
||||
Authentication authentication = new Authentication(user, new RealmRef("__attach", "__attach", nodeName), null);
|
||||
authentication.writeToContextIfMissing(threadContext, cryptoService, signUserHeader);
|
||||
}
|
||||
|
||||
Authenticator createAuthenticator(RestRequest request) {
|
||||
return new Authenticator(request);
|
||||
// pkg private method for testing
|
||||
Authenticator createAuthenticator(RestRequest request, ActionListener<Authentication> listener) {
|
||||
return new Authenticator(request, listener);
|
||||
}
|
||||
|
||||
Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser) {
|
||||
return new Authenticator(action, message, fallbackUser);
|
||||
// pkg private method for testing
|
||||
Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser, ActionListener<Authentication> listener) {
|
||||
return new Authenticator(action, message, fallbackUser, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for taking a request and executing the authentication. The authentication is executed in an asynchronous
|
||||
* fashion in order to avoid blocking calls on a network thread. This class also performs the auditing necessary around authentication
|
||||
*/
|
||||
class Authenticator {
|
||||
|
||||
private final AuditableRequest request;
|
||||
private final User fallbackUser;
|
||||
private final ActionListener<Authentication> listener;
|
||||
|
||||
private RealmRef authenticatedBy = null;
|
||||
private RealmRef lookedupBy = null;
|
||||
private AuthenticationToken authenticationToken = null;
|
||||
|
||||
Authenticator(RestRequest request) {
|
||||
this.request = new Rest(request);
|
||||
this.fallbackUser = null;
|
||||
Authenticator(RestRequest request, ActionListener<Authentication> listener) {
|
||||
this(new AuditableRestRequest(auditTrail, failureHandler, threadContext, request), null, listener);
|
||||
}
|
||||
|
||||
Authenticator(String action, TransportMessage message, User fallbackUser) {
|
||||
this.request = new Transport(action, message);
|
||||
Authenticator(String action, TransportMessage message, User fallbackUser, ActionListener<Authentication> listener) {
|
||||
this(new AuditableTransportRequest(auditTrail, failureHandler, threadContext, action, message), fallbackUser, listener);
|
||||
}
|
||||
|
||||
private Authenticator(AuditableRequest auditableRequest, User fallbackUser, ActionListener<Authentication> listener) {
|
||||
this.request = auditableRequest;
|
||||
this.fallbackUser = fallbackUser;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
Authentication authenticate() throws IOException, IllegalArgumentException {
|
||||
Authentication existing = getCurrentAuthentication();
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
AuthenticationToken token = extractToken();
|
||||
if (token == null) {
|
||||
Authentication authentication = handleNullToken();
|
||||
request.authenticationSuccess(authentication.getAuthenticatedBy().getName(), authentication.getUser());
|
||||
return authentication;
|
||||
}
|
||||
|
||||
User user = authenticateToken(token);
|
||||
if (user == null) {
|
||||
throw handleNullUser(token);
|
||||
}
|
||||
user = lookupRunAsUserIfNecessary(user, token);
|
||||
checkIfUserIsDisabled(user, token);
|
||||
|
||||
final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy);
|
||||
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||
request.authenticationSuccess(authentication.getAuthenticatedBy().getName(), user);
|
||||
return authentication;
|
||||
/**
|
||||
* This method starts the authentication process. The authentication process can be broken down into distinct operations. In order,
|
||||
* these operations are:
|
||||
*
|
||||
* <ol>
|
||||
* <li>look for existing authentication {@link #lookForExistingAuthentication(Consumer)}</li>
|
||||
* <li>token extraction {@link #extractToken(Consumer)}</li>
|
||||
* <li>token authentication {@link #consumeToken(AuthenticationToken)}</li>
|
||||
* <li>user lookup for run as if necessary {@link #consumeUser(User)} and
|
||||
* {@link #lookupRunAsUser(User, String, Consumer)}</li>
|
||||
* <li>write authentication into the context {@link #finishAuthentication(User)}</li>
|
||||
* </ol>
|
||||
*/
|
||||
private void authenticateAsync() {
|
||||
lookForExistingAuthentication((authentication) -> {
|
||||
if (authentication != null) {
|
||||
listener.onResponse(authentication);
|
||||
} else {
|
||||
extractToken(this::consumeToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Authentication getCurrentAuthentication() {
|
||||
Authentication authentication;
|
||||
/**
|
||||
* Looks to see if the request contains an existing {@link Authentication} and if so, that authentication will be used. The
|
||||
* consumer is called if no exception was thrown while trying to read the authentication and may be called with a {@code null}
|
||||
* value
|
||||
*/
|
||||
private void lookForExistingAuthentication(Consumer<Authentication> authenticationConsumer) {
|
||||
Runnable action;
|
||||
try {
|
||||
authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader);
|
||||
final Authentication authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader);
|
||||
if (authentication != null && request instanceof AuditableRestRequest) {
|
||||
action = () -> listener.onFailure(request.tamperedRequest());
|
||||
} else {
|
||||
action = () -> authenticationConsumer.accept(authentication);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw request.tamperedRequest();
|
||||
logger.error((Supplier<?>)
|
||||
() -> new ParameterizedMessage("caught exception while trying to read authentication from request [{}]", request),
|
||||
e);
|
||||
action = () -> listener.onFailure(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;
|
||||
// we use the success boolean as we need to know if the executed code block threw an exception and we already called on
|
||||
// failure; if we did call the listener we do not need to continue. While we could place this call in the try block, the
|
||||
// issue is that we catch all exceptions and could catch exceptions that have nothing to do with a tampered request.
|
||||
action.run();
|
||||
}
|
||||
|
||||
AuthenticationToken extractToken() {
|
||||
AuthenticationToken token = null;
|
||||
/**
|
||||
* Attempts to extract an {@link AuthenticationToken} from the request by iterating over the {@link Realms} and calling
|
||||
* {@link Realm#token(ThreadContext)}. The first non-null token that is returned will be used. The consumer is only called if
|
||||
* no exception was caught during the extraction process and may be called with a {@code null} token.
|
||||
*/
|
||||
// pkg-private accessor testing token extraction with a consumer
|
||||
void extractToken(Consumer<AuthenticationToken> consumer) {
|
||||
Runnable action = () -> consumer.accept(null);
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
token = realm.token(threadContext);
|
||||
final AuthenticationToken token = realm.token(threadContext);
|
||||
if (token != null) {
|
||||
logger.trace("realm [{}] resolved authentication token [{}] from [{}]", realm, token.principal(), request);
|
||||
action = () -> consumer.accept(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug((Supplier<?>) () -> new ParameterizedMessage("failed to extract token from request: [{}]", request), e);
|
||||
} else {
|
||||
logger.warn("failed to extract token from request: [{}]: {}", request, e.getMessage());
|
||||
}
|
||||
throw request.exceptionProcessingRequest(e, null);
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, null));
|
||||
}
|
||||
return token;
|
||||
|
||||
action.run();
|
||||
}
|
||||
|
||||
Authentication handleNullToken() throws IOException {
|
||||
Authentication authentication = null;
|
||||
/**
|
||||
* Consumes the {@link AuthenticationToken} provided by the caller. In the case of a {@code null} token, {@link #handleNullToken()}
|
||||
* is called. In the case of a {@code non-null} token, the realms are iterated over and the first realm that returns a non-null
|
||||
* {@link User} is the authenticating realm and iteration is stopped. This user is then passed to {@link #consumeUser(User)} if no
|
||||
* exception was caught while trying to authenticate the token
|
||||
*/
|
||||
private void consumeToken(AuthenticationToken token) {
|
||||
if (token == null) {
|
||||
handleNullToken();
|
||||
} else {
|
||||
authenticationToken = token;
|
||||
Runnable action = () -> consumeUser(null);
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
User user = authenticateToken(realm);
|
||||
if (user != null) {
|
||||
action = () -> consumeUser(user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, token));
|
||||
}
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles failed extraction of an authentication token. This can happen in a few different scenarios:
|
||||
*
|
||||
* <ul>
|
||||
* <li>this is an initial request from a client without preemptive authentication, so we must return an authentication
|
||||
* challenge</li>
|
||||
* <li>this is a request made internally within a node and there is a fallback user, which is typically the
|
||||
* {@link org.elasticsearch.xpack.security.user.SystemUser}</li>
|
||||
* <li>anonymous access is enabled and this will be considered an anonymous request</li>
|
||||
* </ul>
|
||||
*
|
||||
* Regardless of the scenario, this method will call the listener with either failure or success.
|
||||
*/
|
||||
// pkg-private for tests
|
||||
void handleNullToken() {
|
||||
final Authentication authentication;
|
||||
if (fallbackUser != null) {
|
||||
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
|
||||
authentication = new Authentication(fallbackUser, authenticatedBy, null);
|
||||
} else if (isAnonymousUserEnabled) {
|
||||
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
|
||||
authentication = new Authentication(anonymousUser, authenticatedBy, null);
|
||||
} else {
|
||||
authentication = null;
|
||||
}
|
||||
|
||||
Runnable action;
|
||||
if (authentication != null) {
|
||||
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||
return authentication;
|
||||
try {
|
||||
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||
request.authenticationSuccess(authentication.getAuthenticatedBy().getName(), authentication.getUser());
|
||||
action = () -> listener.onResponse(authentication);
|
||||
} catch (Exception e) {
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
|
||||
}
|
||||
} else {
|
||||
action = () -> listener.onFailure(request.anonymousAccessDenied());
|
||||
}
|
||||
throw request.anonymousAccessDenied();
|
||||
|
||||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing when
|
||||
// an exception bubbles up even after successful authentication
|
||||
action.run();
|
||||
}
|
||||
|
||||
User authenticateToken(AuthenticationToken token) {
|
||||
/**
|
||||
* Encapsulates the interaction with the realm and audit trail when attempting to authenticate a token. If the realm supports the
|
||||
* token, authentication will be attempted. A successful authentication results in returning a non-null user in addition to setting
|
||||
* the authenticatedBy value. A failed authentication will result in returning {@code null}
|
||||
*/
|
||||
private User authenticateToken(Realm realm) {
|
||||
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());
|
||||
}
|
||||
if (realm.supports(authenticationToken)) {
|
||||
user = realm.authenticate(authenticationToken);
|
||||
if (user == null) {
|
||||
request.realmAuthenticationFailed(authenticationToken, realm.name());
|
||||
} else {
|
||||
authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"authentication failed for principal [{}], [{}] ", token.principal(), request), e);
|
||||
throw request.exceptionProcessingRequest(e, token);
|
||||
} finally {
|
||||
token.clearCredentials();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
ElasticsearchSecurityException handleNullUser(AuthenticationToken token) {
|
||||
throw request.authenticationFailed(token);
|
||||
}
|
||||
|
||||
boolean shouldTryToRunAs(User authenticatedUser, AuthenticationToken token) {
|
||||
if (runAsEnabled == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER);
|
||||
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, runAsUser);
|
||||
return user;
|
||||
}
|
||||
/**
|
||||
* Consumes the {@link User} that resulted from attempting to authenticate a token against the {@link Realms}. When the user is
|
||||
* {@code null}, authentication fails and does not proceed. When there is a user, the request is inspected to see if the run as
|
||||
* functionality is in use. When run as is not in use, {@link #finishAuthentication(User)} is called, otherwise we try to lookup
|
||||
* the run as user in {@link #lookupRunAsUser(User, String, Consumer)}
|
||||
*/
|
||||
private void consumeUser(User user) {
|
||||
if (user == null) {
|
||||
listener.onFailure(request.authenticationFailed(authenticationToken));
|
||||
} else {
|
||||
if (runAsEnabled) {
|
||||
final String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER);
|
||||
if (runAsUsername != null && runAsUsername.isEmpty() == false) {
|
||||
lookupRunAsUser(user, runAsUsername, this::finishAuthentication);
|
||||
} else if (runAsUsername == null) {
|
||||
finishAuthentication(user);
|
||||
} else {
|
||||
assert runAsUsername.isEmpty() : "the run as username may not be empty";
|
||||
logger.debug("user [{}] attempted to runAs with an empty username", user.principal());
|
||||
listener.onFailure(request.runAsDenied(new User(user.principal(), user.roles(),
|
||||
new User(runAsUsername, Strings.EMPTY_ARRAY)), authenticationToken));
|
||||
}
|
||||
} else {
|
||||
finishAuthentication(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
/**
|
||||
* Iterates over the realms and attempts to lookup the run as user by the given username. The consumer will be called regardless of
|
||||
* if the user is found or not, with a non-null user. We do not fail requests if the run as user is not found as that can leak the
|
||||
* names of users that exist using a timing attack
|
||||
*/
|
||||
private void lookupRunAsUser(final User user, String runAsUsername, Consumer<User> userConsumer) {
|
||||
// FIXME there are certain actions that could be allowed now with the default role that we should probably bail on!
|
||||
Runnable action = () -> {
|
||||
if (lookedupBy != null) {
|
||||
throw new IllegalStateException("we could not lookup the user but created a realm reference");
|
||||
// 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
|
||||
if (lookedupBy != null) {
|
||||
throw new IllegalStateException("we could not lookup the user but created a realm reference");
|
||||
}
|
||||
} else {
|
||||
userConsumer.accept(new User(user, new User(runAsUsername, Strings.EMPTY_ARRAY)));
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
User runAsUser = lookupUser(realm, runAsUsername);
|
||||
if (runAsUser != null) {
|
||||
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
action = () -> userConsumer.accept(new User(user, runAsUser));
|
||||
break;
|
||||
}
|
||||
}
|
||||
user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY));
|
||||
} catch (Exception e) {
|
||||
logger.debug(
|
||||
(Supplier<?>) () -> new ParameterizedMessage("run as failed for principal [{}], [{}], run as username [{}]",
|
||||
token.principal(),
|
||||
request,
|
||||
runAsUsername),
|
||||
e);
|
||||
throw request.exceptionProcessingRequest(e, token);
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
|
||||
}
|
||||
return user;
|
||||
|
||||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing when
|
||||
// an exception bubbles up even after successful authentication
|
||||
action.run();
|
||||
}
|
||||
|
||||
void checkIfUserIsDisabled(User user, AuthenticationToken token) {
|
||||
if (user.enabled() == false || (user.runAs() != null && user.runAs().enabled() == false)) {
|
||||
logger.debug("user [{}] is disabled. failing authentication", user);
|
||||
throw request.authenticationFailed(token);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AuditableRequest {
|
||||
|
||||
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
||||
|
||||
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);
|
||||
|
||||
abstract void authenticationSuccess(String realm, User user);
|
||||
}
|
||||
|
||||
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 authenticationSuccess(String realm, User user) {
|
||||
auditTrail.authenticationSuccess(realm, user, action, 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);
|
||||
/**
|
||||
* Handles the interaction with the realm and trying to lookup a user. If a user is found, this method also creates the
|
||||
* {@link RealmRef} that identifies the realm that looked up the user
|
||||
*/
|
||||
private User lookupUser(Realm realm, String runAsUsername) {
|
||||
User lookedUp = null;
|
||||
if (realm.userLookupSupported()) {
|
||||
lookedUp = realm.lookupUser(runAsUsername);
|
||||
if (lookedUp != null) {
|
||||
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
}
|
||||
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
|
||||
}
|
||||
return lookedUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
|
||||
/**
|
||||
* Finishes the authentication process by ensuring the returned user is enabled and that the run as user is enabled if there is
|
||||
* one. If authentication is successful, this method also ensures that the authentication is written to the ThreadContext
|
||||
*/
|
||||
void finishAuthentication(User finalUser) {
|
||||
if (finalUser.enabled() == false || (finalUser.runAs() != null && finalUser.runAs().enabled() == false)) {
|
||||
logger.debug("user [{}] is disabled. failing authentication", finalUser);
|
||||
listener.onFailure(request.authenticationFailed(authenticationToken));
|
||||
} else {
|
||||
request.authenticationSuccess(authenticatedBy.getName(), finalUser);
|
||||
final Authentication finalAuth = new Authentication(finalUser, authenticatedBy, lookedupBy);
|
||||
Runnable action = () -> listener.onResponse(finalAuth);
|
||||
try {
|
||||
finalAuth.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||
} catch (Exception e) {
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
|
||||
}
|
||||
|
||||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing
|
||||
// when an exception bubbles up even after successful authentication
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AuditableRequest {
|
||||
|
||||
final AuditTrail auditTrail;
|
||||
final AuthenticationFailureHandler failureHandler;
|
||||
final ThreadContext threadContext;
|
||||
|
||||
AuditableRequest(AuditTrail auditTrail, AuthenticationFailureHandler failureHandler, ThreadContext threadContext) {
|
||||
this.auditTrail = auditTrail;
|
||||
this.failureHandler = failureHandler;
|
||||
this.threadContext = threadContext;
|
||||
}
|
||||
|
||||
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
||||
|
||||
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);
|
||||
|
||||
abstract void authenticationSuccess(String realm, User user);
|
||||
}
|
||||
|
||||
static class AuditableTransportRequest extends AuditableRequest {
|
||||
|
||||
private final String action;
|
||||
private final TransportMessage message;
|
||||
|
||||
AuditableTransportRequest(AuditTrail auditTrail, AuthenticationFailureHandler failureHandler, ThreadContext threadContext,
|
||||
String action, TransportMessage message) {
|
||||
super(auditTrail, failureHandler, threadContext);
|
||||
this.action = action;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
void authenticationSuccess(String realm, User user) {
|
||||
auditTrail.authenticationSuccess(realm, user, action, 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);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "transport request action [" + action + "]";
|
||||
} else {
|
||||
auditTrail.authenticationFailed(action, message);
|
||||
}
|
||||
return failureHandler.exceptionProcessingRequest(message, action, e, threadContext);
|
||||
}
|
||||
|
||||
class Rest extends AuditableRequest {
|
||||
@Override
|
||||
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
|
||||
auditTrail.authenticationFailed(token, action, message);
|
||||
return failureHandler.failedAuthentication(message, token, action, threadContext);
|
||||
}
|
||||
|
||||
private final RestRequest request;
|
||||
@Override
|
||||
ElasticsearchSecurityException anonymousAccessDenied() {
|
||||
auditTrail.anonymousAccessDenied(action, message);
|
||||
return failureHandler.missingToken(message, action, threadContext);
|
||||
}
|
||||
|
||||
Rest(RestRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
@Override
|
||||
ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) {
|
||||
auditTrail.runAsDenied(user, action, message);
|
||||
return failureHandler.failedAuthentication(message, token, action, threadContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
void authenticationSuccess(String realm, User user) {
|
||||
auditTrail.authenticationSuccess(realm, user, request);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "transport request action [" + action + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void realmAuthenticationFailed(AuthenticationToken token, String realm) {
|
||||
auditTrail.authenticationFailed(realm, token, request);
|
||||
}
|
||||
static class AuditableRestRequest extends AuditableRequest {
|
||||
|
||||
@Override
|
||||
ElasticsearchSecurityException tamperedRequest() {
|
||||
auditTrail.tamperedRequest(request);
|
||||
return new ElasticsearchSecurityException("rest request attempted to inject a user");
|
||||
}
|
||||
private final RestRequest request;
|
||||
|
||||
@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);
|
||||
}
|
||||
AuditableRestRequest(AuditTrail auditTrail, AuthenticationFailureHandler failureHandler, ThreadContext threadContext,
|
||||
RestRequest request) {
|
||||
super(auditTrail, failureHandler, threadContext);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
|
||||
@Override
|
||||
void authenticationSuccess(String realm, User user) {
|
||||
auditTrail.authenticationSuccess(realm, user, 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);
|
||||
return failureHandler.failedAuthentication(request, token, threadContext);
|
||||
} else {
|
||||
auditTrail.authenticationFailed(request);
|
||||
}
|
||||
return failureHandler.exceptionProcessingRequest(request, e, threadContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
ElasticsearchSecurityException anonymousAccessDenied() {
|
||||
auditTrail.anonymousAccessDenied(request);
|
||||
return failureHandler.missingToken(request, threadContext);
|
||||
}
|
||||
@Override
|
||||
ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) {
|
||||
auditTrail.authenticationFailed(token, request);
|
||||
return failureHandler.failedAuthentication(request, token, threadContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) {
|
||||
auditTrail.runAsDenied(user, request);
|
||||
return failureHandler.failedAuthentication(request, token, threadContext);
|
||||
}
|
||||
@Override
|
||||
ElasticsearchSecurityException anonymousAccessDenied() {
|
||||
auditTrail.anonymousAccessDenied(request);
|
||||
return failureHandler.missingToken(request, threadContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "rest request uri [" + request.uri() + "]";
|
||||
}
|
||||
@Override
|
||||
ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) {
|
||||
auditTrail.runAsDenied(user, request);
|
||||
return failureHandler.failedAuthentication(request, token, threadContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "rest request uri [" + request.uri() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.rest;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
|
@ -21,6 +22,7 @@ import org.elasticsearch.rest.RestController;
|
|||
import org.elasticsearch.rest.RestFilter;
|
||||
import org.elasticsearch.rest.RestFilterChain;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestRequest.Method;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||
import org.elasticsearch.xpack.security.authc.pki.PkiRealm;
|
||||
|
@ -40,6 +42,7 @@ public class SecurityRestFilter extends RestFilter {
|
|||
private final Logger logger;
|
||||
private final XPackLicenseState licenseState;
|
||||
private final ThreadContext threadContext;
|
||||
private final RestController restController;
|
||||
private final boolean extractClientCertificate;
|
||||
|
||||
@Inject
|
||||
|
@ -53,6 +56,7 @@ public class SecurityRestFilter extends RestFilter {
|
|||
Settings httpSSLSettings = SSLService.getHttpTransportSSLSettings(settings);
|
||||
this.extractClientCertificate = ssl && sslService.isSSLClientAuthEnabled(httpSSLSettings);
|
||||
controller.registerFilter(this);
|
||||
this.restController = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,19 +67,18 @@ public class SecurityRestFilter extends RestFilter {
|
|||
@Override
|
||||
public void process(RestRequest request, RestChannel channel, NodeClient client, RestFilterChain filterChain) throws Exception {
|
||||
|
||||
if (licenseState.isAuthAllowed()) {
|
||||
if (licenseState.isAuthAllowed() && request.method() != Method.OPTIONS) {
|
||||
// CORS - allow for preflight unauthenticated OPTIONS request
|
||||
if (request.method() != RestRequest.Method.OPTIONS) {
|
||||
if (extractClientCertificate) {
|
||||
putClientCertificateInContext(request, threadContext, logger);
|
||||
}
|
||||
service.authenticate(request).getUser();
|
||||
if (extractClientCertificate) {
|
||||
putClientCertificateInContext(request, threadContext, logger);
|
||||
}
|
||||
|
||||
RemoteHostHeader.process(request, threadContext);
|
||||
service.authenticate(request, ActionListener.wrap((authentication) -> {
|
||||
RemoteHostHeader.process(request, threadContext);
|
||||
filterChain.continueProcessing(request, channel, client);
|
||||
}, (e) -> restController.sendErrorResponse(request, channel, e)));
|
||||
} else {
|
||||
filterChain.continueProcessing(request, channel, client);
|
||||
}
|
||||
|
||||
filterChain.continueProcessing(request, channel, client);
|
||||
}
|
||||
|
||||
static void putClientCertificateInContext(RestRequest request, ThreadContext threadContext, Logger logger) throws Exception {
|
||||
|
|
|
@ -201,7 +201,6 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
|
|||
}
|
||||
};
|
||||
try (ThreadContext.StoredContext ctx = threadContext.newStoredContext()) {
|
||||
|
||||
if (licenseState.isAuthAllowed()) {
|
||||
String profile = channel.getProfileName();
|
||||
ServerTransportFilter filter = profileFilters.get(profile);
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.elasticsearch.transport.TcpTransportChannel;
|
|||
import org.elasticsearch.transport.TransportChannel;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
import org.elasticsearch.xpack.security.action.SecurityActionMapper;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||
import org.elasticsearch.xpack.security.authc.pki.PkiRealm;
|
||||
import org.elasticsearch.xpack.security.authz.AuthorizationService;
|
||||
|
@ -116,13 +115,15 @@ public interface ServerTransportFilter {
|
|||
}
|
||||
}
|
||||
}
|
||||
final Authentication authentication = authcService.authenticate(securityAction, request, null);
|
||||
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer = new AuthorizationUtils.AsyncAuthorizer(authentication, listener,
|
||||
(userRoles, runAsRoles) -> {
|
||||
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
|
||||
listener.onResponse(null);
|
||||
});
|
||||
asyncAuthorizer.authorize(authzService);
|
||||
|
||||
authcService.authenticate(securityAction, request, null, ActionListener.wrap((authentication) -> {
|
||||
final AuthorizationUtils.AsyncAuthorizer asyncAuthorizer =
|
||||
new AuthorizationUtils.AsyncAuthorizer(authentication, listener, (userRoles, runAsRoles) -> {
|
||||
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
|
||||
listener.onResponse(null);
|
||||
});
|
||||
asyncAuthorizer.authorize(authzService);
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
private void extactClientCertificates(SSLEngine sslEngine, Object channel) {
|
||||
|
|
|
@ -89,7 +89,12 @@ public class SecurityActionFilterTests extends ESTestCase {
|
|||
Task task = mock(Task.class);
|
||||
User user = new User("username", "r1", "r2");
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
|
@ -112,7 +117,12 @@ public class SecurityActionFilterTests extends ESTestCase {
|
|||
Task task = mock(Task.class);
|
||||
User user = new User("username", "r1", "r2");
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate(action, request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
|
@ -137,7 +147,12 @@ public class SecurityActionFilterTests extends ESTestCase {
|
|||
Task task = mock(Task.class);
|
||||
User user = new User("username", "r1", "r2");
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
|
@ -158,7 +173,12 @@ public class SecurityActionFilterTests extends ESTestCase {
|
|||
User user = mock(User.class);
|
||||
Task task = mock(Task.class);
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
|
||||
when(cryptoService.isSigned("signed_scroll_id")).thenReturn(true);
|
||||
when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
|
||||
doAnswer((i) -> {
|
||||
|
@ -182,7 +202,12 @@ public class SecurityActionFilterTests extends ESTestCase {
|
|||
User user = mock(User.class);
|
||||
Task task = mock(Task.class);
|
||||
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
|
||||
when(authcService.authenticate("_action", request, SystemUser.INSTANCE)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
|
||||
when(cryptoService.isSigned("scroll_id")).thenReturn(true);
|
||||
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
|
||||
doAnswer((i) -> {
|
||||
|
|
|
@ -11,6 +11,8 @@ import java.util.Collections;
|
|||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
@ -116,18 +118,24 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
|
||||
Authenticator authenticator = service.createAuthenticator("_action", message, null);
|
||||
AuthenticationToken result = authenticator.extractToken();
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
|
||||
Authenticator authenticator = service.createAuthenticator("_action", message, null, future);
|
||||
authenticator.extractToken((result) -> {
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
});
|
||||
}
|
||||
|
||||
public void testTokenMissing() throws Exception {
|
||||
Authenticator authenticator = service.createAuthenticator("_action", message, null);
|
||||
AuthenticationToken token = authenticator.extractToken();
|
||||
assertThat(token, nullValue());
|
||||
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, authenticator::handleNullToken);
|
||||
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
|
||||
Authenticator authenticator = service.createAuthenticator("_action", message, null, future);
|
||||
authenticator.extractToken((token) -> {
|
||||
assertThat(token, nullValue());
|
||||
authenticator.handleNullToken();
|
||||
});
|
||||
|
||||
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> future.actionGet());
|
||||
assertThat(e.getMessage(), containsString("missing authentication token"));
|
||||
verify(auditTrail).anonymousAccessDenied("_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
|
@ -146,7 +154,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
}
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), is(user));
|
||||
assertThat(result.getLookedUpBy(), is(nullValue()));
|
||||
|
@ -163,7 +171,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), is(user));
|
||||
verify(auditTrail).authenticationSuccess(secondRealm.name(), user, "_action", message);
|
||||
|
@ -176,7 +184,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
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);
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(authentication));
|
||||
|
@ -190,7 +198,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(new UsernamePasswordToken("idonotexist",
|
||||
new SecuredString("passwd".toCharArray())));
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("Authentication was successful but should not");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("unable to authenticate user [idonotexist] for REST request [/]"));
|
||||
|
@ -201,10 +209,10 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
when(secondRealm.token(threadContext)).thenReturn(null);
|
||||
|
||||
Authenticator authenticator = service.createAuthenticator(restRequest);
|
||||
AuthenticationToken token = authenticator.extractToken();
|
||||
|
||||
assertThat(token, nullValue());
|
||||
Authenticator authenticator = service.createAuthenticator(restRequest, mock(ActionListener.class));
|
||||
authenticator.extractToken((token) -> {
|
||||
assertThat(token, nullValue());
|
||||
});
|
||||
}
|
||||
|
||||
public void authenticationInContextAndHeader() throws Exception {
|
||||
|
@ -213,7 +221,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), is(user));
|
||||
|
@ -230,7 +238,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
when(secondRealm.token(threadContext)).thenReturn(null);
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("expected an authentication exception when trying to authenticate an anonymous message");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
// expected
|
||||
|
@ -243,7 +251,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(null);
|
||||
when(secondRealm.token(threadContext)).thenReturn(null);
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("expected an authentication exception when trying to authenticate an anonymous message");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
// expected
|
||||
|
@ -257,7 +265,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.token(threadContext)).thenReturn(null);
|
||||
User user1 = new User("username", "r1", "r2");
|
||||
|
||||
Authentication result = service.authenticate("_action", message, user1);
|
||||
Authentication result = authenticateBlocking("_action", message, user1);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
|
@ -271,7 +279,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate("_action", message, fallback));
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", message, fallback));
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
|
@ -284,7 +292,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate(restRequest));
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking(restRequest));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
|
@ -297,7 +305,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
Authentication result = service.authenticate("_action", message, fallback);
|
||||
Authentication result = authenticateBlocking("_action", message, fallback);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
|
@ -310,7 +318,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
Authentication result = service.authenticate(restRequest);
|
||||
Authentication result = authenticateBlocking(restRequest);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
|
@ -323,7 +331,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
Authentication authentication = authenticateBlocking("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(authentication);
|
||||
|
@ -338,7 +346,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
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);
|
||||
Authentication ctxAuth = authenticateBlocking("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(ctxAuth, sameInstance(authentication));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
reset(firstRealm);
|
||||
|
@ -360,7 +368,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
Authentication result = authenticateBlocking("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
|
@ -375,7 +383,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
Authentication authentication = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
Authentication authentication = authenticateBlocking("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
assertThreadContextContainsAuthentication(authentication, false);
|
||||
|
@ -389,7 +397,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
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);
|
||||
Authentication ctxAuth = authenticateBlocking("_action", message1, SystemUser.INSTANCE);
|
||||
assertThat(ctxAuth, sameInstance(authentication));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
reset(firstRealm);
|
||||
|
@ -407,7 +415,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
Authentication result = authenticateBlocking("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
verifyZeroInteractions(firstRealm);
|
||||
|
@ -422,7 +430,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
randomFrom(new RuntimeException(), new IllegalArgumentException(), new IllegalStateException()));
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, randomBoolean() ? SystemUser.INSTANCE : null);
|
||||
authenticateBlocking("_action", message, randomBoolean() ? SystemUser.INSTANCE : null);
|
||||
} catch (Exception e) {
|
||||
//expected
|
||||
verify(auditTrail).tamperedRequest("_action", message);
|
||||
|
@ -472,7 +480,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadPool, anonymousUser);
|
||||
RestRequest request = new FakeRestRequest();
|
||||
|
||||
Authentication result = service.authenticate(request);
|
||||
Authentication result = authenticateBlocking(request);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance((Object) anonymousUser));
|
||||
|
@ -490,7 +498,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(anonymousUser));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
|
@ -506,7 +514,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
Authentication result = service.authenticate("_action", message, SystemUser.INSTANCE);
|
||||
Authentication result = authenticateBlocking("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(SystemUser.INSTANCE));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
|
@ -515,7 +523,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
public void testRealmTokenThrowingException() throws Exception {
|
||||
when(firstRealm.token(threadContext)).thenThrow(authenticationError("realm doesn't like tokens"));
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like tokens"));
|
||||
|
@ -526,7 +534,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
public void testRealmTokenThrowingExceptionRest() throws Exception {
|
||||
when(firstRealm.token(threadContext)).thenThrow(authenticationError("realm doesn't like tokens"));
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like tokens"));
|
||||
|
@ -539,7 +547,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenThrow(authenticationError("realm doesn't like supports"));
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like supports"));
|
||||
|
@ -552,7 +560,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenThrow(authenticationError("realm doesn't like supports"));
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like supports"));
|
||||
|
@ -566,7 +574,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenThrow(authenticationError("realm doesn't like authenticate"));
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like authenticate"));
|
||||
|
@ -580,9 +588,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenThrow(authenticationError("realm doesn't like authenticate"));
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't like authenticate"));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
}
|
||||
|
@ -598,7 +606,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't want to lookup"));
|
||||
|
@ -616,7 +624,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't want to lookup"));
|
||||
|
@ -637,9 +645,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
Authentication result;
|
||||
if (randomBoolean()) {
|
||||
result = service.authenticate("_action", message, null);
|
||||
result = authenticateBlocking("_action", message, null);
|
||||
} else {
|
||||
result = service.authenticate(restRequest);
|
||||
result = authenticateBlocking(restRequest);
|
||||
}
|
||||
assertThat(result, notNullValue());
|
||||
User authenticated = result.getUser();
|
||||
|
@ -670,9 +678,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
Authentication result;
|
||||
if (randomBoolean()) {
|
||||
result = service.authenticate("_action", message, null);
|
||||
result = authenticateBlocking("_action", message, null);
|
||||
} else {
|
||||
result = service.authenticate(restRequest);
|
||||
result = authenticateBlocking(restRequest);
|
||||
}
|
||||
assertThat(result, notNullValue());
|
||||
User authenticated = result.getUser();
|
||||
|
@ -696,7 +704,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).runAsDenied(any(User.class), eq(restRequest));
|
||||
|
@ -714,7 +722,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).runAsDenied(any(User.class), eq("_action"), eq(message));
|
||||
|
@ -733,7 +741,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate("_action", message, fallback));
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", message, fallback));
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
|
@ -750,7 +758,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate(restRequest));
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking(restRequest));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
|
@ -773,4 +781,16 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
assertThat(threadContext.getHeader(Authentication.AUTHENTICATION_KEY), equalTo((Object) authentication.encode()));
|
||||
}
|
||||
}
|
||||
|
||||
private Authentication authenticateBlocking(RestRequest restRequest) {
|
||||
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
|
||||
service.authenticate(restRequest, future);
|
||||
return future.actionGet();
|
||||
}
|
||||
|
||||
private Authentication authenticateBlocking(String action, TransportMessage message, User fallbackUser) {
|
||||
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
|
||||
service.authenticate(action, message, fallbackUser, future);
|
||||
return future.actionGet();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.rest;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.rest.RestChannel;
|
||||
|
@ -21,7 +21,9 @@ import org.elasticsearch.xpack.ssl.SSLService;
|
|||
import org.junit.Before;
|
||||
|
||||
import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
@ -33,11 +35,12 @@ public class SecurityRestFilterTests extends ESTestCase {
|
|||
private RestFilterChain chain;
|
||||
private SecurityRestFilter filter;
|
||||
private XPackLicenseState licenseState;
|
||||
private RestController restController;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
authcService = mock(AuthenticationService.class);
|
||||
RestController restController = mock(RestController.class);
|
||||
restController = mock(RestController.class);
|
||||
channel = mock(RestChannel.class);
|
||||
chain = mock(RestFilterChain.class);
|
||||
licenseState = mock(XPackLicenseState.class);
|
||||
|
@ -51,7 +54,12 @@ public class SecurityRestFilterTests extends ESTestCase {
|
|||
public void testProcess() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authcService.authenticate(request)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(request), any(ActionListener.class));
|
||||
filter.process(request, channel, null, chain);
|
||||
verify(chain).continueProcessing(request, channel, null);
|
||||
verifyZeroInteractions(channel);
|
||||
|
@ -67,13 +75,15 @@ public class SecurityRestFilterTests extends ESTestCase {
|
|||
|
||||
public void testProcessAuthenticationError() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
when(authcService.authenticate(request)).thenThrow(authenticationError("failed authc"));
|
||||
try {
|
||||
filter.process(request, channel, null, chain);
|
||||
fail("expected rest filter process to throw an authentication exception when authentication fails");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), equalTo("failed authc"));
|
||||
}
|
||||
Exception exception = authenticationError("failed authc");
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
callback.onFailure(exception);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(request), any(ActionListener.class));
|
||||
filter.process(request, channel, null, chain);
|
||||
verify(restController).sendErrorResponse(request, channel, exception);
|
||||
verifyZeroInteractions(channel);
|
||||
verifyZeroInteractions(chain);
|
||||
}
|
||||
|
|
|
@ -74,7 +74,13 @@ public class ServerTransportFilterTests extends ESTestCase {
|
|||
TransportRequest request = mock(TransportRequest.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authentication.getUser()).thenReturn(SystemUser.INSTANCE);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(SystemUser.INSTANCE);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(null), any(ActionListener.class));
|
||||
ServerTransportFilter filter = getClientOrNodeFilter();
|
||||
PlainActionFuture<Void> future = new PlainActionFuture<>();
|
||||
filter.inbound("_action", request, channel, future);
|
||||
|
@ -89,7 +95,12 @@ public class ServerTransportFilterTests extends ESTestCase {
|
|||
randomFrom("*", "_all", "test*"));
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authentication.getUser()).thenReturn(SystemUser.INSTANCE);
|
||||
when(authcService.authenticate(action, request, null)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(action), eq(request), eq(null), any(ActionListener.class));
|
||||
ServerTransportFilter filter = getClientOrNodeFilter();
|
||||
PlainActionFuture listener = mock(PlainActionFuture.class);
|
||||
filter.inbound(action, request, channel, listener);
|
||||
|
@ -102,7 +113,13 @@ public class ServerTransportFilterTests extends ESTestCase {
|
|||
|
||||
public void testInboundAuthenticationException() throws Exception {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
doThrow(authenticationError("authc failed")).when(authcService).authenticate("_action", request, null);
|
||||
Exception authE = authenticationError("authc failed");
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onFailure(authE);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(null), any(ActionListener.class));
|
||||
ServerTransportFilter filter = getClientOrNodeFilter();
|
||||
try {
|
||||
PlainActionFuture<Void> future = new PlainActionFuture<>();
|
||||
|
@ -119,7 +136,12 @@ public class ServerTransportFilterTests extends ESTestCase {
|
|||
ServerTransportFilter filter = getClientOrNodeFilter();
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq("_action"), eq(request), eq(null), any(ActionListener.class));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[1];
|
||||
|
@ -166,16 +188,26 @@ public class ServerTransportFilterTests extends ESTestCase {
|
|||
callback.onResponse(authentication.getUser().equals(i.getArguments()[0]) ? userRoles : Collections.emptyList());
|
||||
return Void.TYPE;
|
||||
}).when(authzService).roles(any(User.class), any(ActionListener.class));
|
||||
when(authcService.authenticate(internalAction, request, null)).thenReturn(authentication);
|
||||
when(authcService.authenticate(nodeOrShardAction, request, null)).thenReturn(authentication);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(internalAction), eq(request), eq(null), any(ActionListener.class));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback =
|
||||
(ActionListener) i.getArguments()[3];
|
||||
callback.onResponse(authentication);
|
||||
return Void.TYPE;
|
||||
}).when(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(null), any(ActionListener.class));
|
||||
|
||||
filter.inbound(internalAction, request, channel, new PlainActionFuture<>());
|
||||
verify(authcService).authenticate(internalAction, request, null);
|
||||
verify(authcService).authenticate(eq(internalAction), eq(request), eq(null), any(ActionListener.class));
|
||||
verify(authzService).roles(eq(authentication.getUser()), any(ActionListener.class));
|
||||
verify(authzService).authorize(authentication, internalAction, request, userRoles, Collections.emptyList());
|
||||
|
||||
filter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>());
|
||||
verify(authcService).authenticate(nodeOrShardAction, request, null);
|
||||
verify(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(null), any(ActionListener.class));
|
||||
verify(authzService, times(2)).roles(eq(authentication.getUser()), any(ActionListener.class));
|
||||
verify(authzService).authorize(authentication, nodeOrShardAction, request, userRoles, Collections.emptyList());
|
||||
verifyNoMoreInteractions(authcService, authzService);
|
||||
|
|
Loading…
Reference in New Issue