Avoid re-authenticating on in-cluster requests
Now, on first successful authentication, we put the user in the message header so it'll be send with any subsequent cluster internal requests (e.g. shard level search) to avoid re-authentication on every node in the cluster. We can do that now, as with multi-binding transport we can guarantee isolation of the internal cluster from client communication. While it's generally safe for transmission, the user header that is sent between the nodes is still signed using the `system_key` as yet another security layer. As part of this change, also added/changed: - A new audit log entry - anonymous access for Rest request. - Changed how system user is assumed. Previously, system user was assumed on the receiving node when no user was associated with the request. Now the system user is assumed on the sending node, meaning, when a node sends a system originated request, initially this request won't be associated with a user. Shield now picks those requests up and attaches the system user to the role and then sends it together with the request. This has two advantages: 1) it's safer to assume system locally where the requests originate from. 2) this will prevent nodes without shield from connecting to nodes with shield. (currently, the attached users are signed using the system key for safety, though this behaviour may be disabled in the settings). - System realm is now removed (no need for that as the system user itself is serialized/attached to the requests) - Fixed some bugs in the tests Closes elastic/elasticsearch#215 Original commit: elastic/x-pack-elasticsearch@3172f5d126
This commit is contained in:
parent
2b108203fb
commit
6087480368
|
@ -19,13 +19,11 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.rest.*;
|
import org.elasticsearch.rest.*;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||||
import org.elasticsearch.shield.authz.SystemRole;
|
|
||||||
import org.elasticsearch.shield.key.KeyService;
|
import org.elasticsearch.shield.key.KeyService;
|
||||||
import org.elasticsearch.shield.key.SignatureException;
|
import org.elasticsearch.shield.key.SignatureException;
|
||||||
|
import org.elasticsearch.shield.transport.ClientTransportFilter;
|
||||||
import org.elasticsearch.shield.transport.ServerTransportFilter;
|
import org.elasticsearch.shield.transport.ServerTransportFilter;
|
||||||
import org.elasticsearch.transport.TransportRequest;
|
import org.elasticsearch.transport.TransportRequest;
|
||||||
|
|
||||||
|
@ -51,22 +49,14 @@ public class SecurityFilter extends AbstractComponent {
|
||||||
this.auditTrail = auditTrail;
|
this.auditTrail = auditTrail;
|
||||||
}
|
}
|
||||||
|
|
||||||
User authenticateAndAuthorize(String action, TransportRequest request) {
|
User authenticateAndAuthorize(String action, TransportRequest request, User fallbackUser) {
|
||||||
|
User user = authcService.authenticate(action, request, fallbackUser);
|
||||||
// if the action is a system action, we'll fall back on the system user, otherwise we
|
|
||||||
// won't fallback on any user and an authentication exception will be thrown
|
|
||||||
AuthenticationToken defaultToken = SystemRole.INSTANCE.check(action) ? SystemRealm.TOKEN : null;
|
|
||||||
|
|
||||||
AuthenticationToken token = authcService.token(action, request, defaultToken);
|
|
||||||
User user = authcService.authenticate(action, request, token);
|
|
||||||
|
|
||||||
authzService.authorize(user, action, request);
|
authzService.authorize(user, action, request);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
User authenticate(RestRequest request) {
|
User authenticate(RestRequest request) {
|
||||||
AuthenticationToken token = authcService.token(request);
|
return authcService.authenticate(request);
|
||||||
return authcService.authenticate(request, token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<Request extends ActionRequest> Request unsign(User user, String action, Request request) {
|
<Request extends ActionRequest> Request unsign(User user, String action, Request request) {
|
||||||
|
@ -151,7 +141,26 @@ public class SecurityFilter extends AbstractComponent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inbound(String action, TransportRequest request) {
|
public void inbound(String action, TransportRequest request) {
|
||||||
filter.authenticateAndAuthorize(action, request);
|
// here we don't have a fallback user, as all incoming request are
|
||||||
|
// expected to have a user attached (either in headers or in context)
|
||||||
|
filter.authenticateAndAuthorize(action, request, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClientTransport implements ClientTransportFilter {
|
||||||
|
|
||||||
|
private final SecurityFilter filter;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ClientTransport(SecurityFilter filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void outbound(String action, TransportRequest request) {
|
||||||
|
// this will check if there's a user associated with the request. If there isn't,
|
||||||
|
// the system user will be attached.
|
||||||
|
filter.authcService.attachUserHeaderIfMissing(request, User.SYSTEM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +176,17 @@ public class SecurityFilter extends AbstractComponent {
|
||||||
@Override
|
@Override
|
||||||
public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) {
|
public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) {
|
||||||
try {
|
try {
|
||||||
User user = filter.authenticateAndAuthorize(action, request);
|
/**
|
||||||
|
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
|
||||||
|
by user interaction. Since these requests are triggered by es core modules, they are security
|
||||||
|
agnostic and therefore not associated with any user. When these requests execute locally, they
|
||||||
|
are executed directly on their relevant action. Since there is no other way a request can make
|
||||||
|
it to the action without an associated user (not via REST or transport - this is taken care of by
|
||||||
|
the {@link Rest} filter and the {@link ServerTransport} filter respectively), it's safe to assume a system user
|
||||||
|
here if a request is not associated with any other user.
|
||||||
|
*/
|
||||||
|
User user = filter.authenticateAndAuthorize(action, request, User.SYSTEM);
|
||||||
request = filter.unsign(user, action, request);
|
request = filter.unsign(user, action, request);
|
||||||
chain.proceed(action, request, new SigningListener(user, action, filter, listener));
|
chain.proceed(action, request, new SigningListener(user, action, filter, listener));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
|
@ -47,12 +47,12 @@ public class ShieldModule extends AbstractShieldModule.Spawn implements PreProce
|
||||||
|
|
||||||
// spawn needed parts in client mode
|
// spawn needed parts in client mode
|
||||||
if (clientMode) {
|
if (clientMode) {
|
||||||
return ImmutableList.of(
|
return ImmutableList.<Module>of(
|
||||||
new SecuredTransportModule(settings),
|
new SecuredTransportModule(settings),
|
||||||
new SSLModule(settings));
|
new SSLModule(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImmutableList.of(
|
return ImmutableList.<Module>of(
|
||||||
new AuthenticationModule(settings),
|
new AuthenticationModule(settings),
|
||||||
new AuthorizationModule(settings),
|
new AuthorizationModule(settings),
|
||||||
new AuditTrailModule(settings),
|
new AuditTrailModule(settings),
|
||||||
|
|
|
@ -5,8 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield;
|
package org.elasticsearch.shield;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.shield.authz.SystemRole;
|
import org.elasticsearch.shield.authz.SystemRole;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +35,29 @@ public abstract class User {
|
||||||
return this == SYSTEM;
|
return this == SYSTEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static User readFrom(StreamInput input) throws IOException {
|
||||||
|
if (input.readBoolean()) {
|
||||||
|
String name = input.readString();
|
||||||
|
if (!System.NAME.equals(name)) {
|
||||||
|
throw new ShieldException("Invalid system user");
|
||||||
|
}
|
||||||
|
return SYSTEM;
|
||||||
|
}
|
||||||
|
return new Simple(input.readString(), input.readStringArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeTo(User user, StreamOutput output) throws IOException {
|
||||||
|
if (user.isSystem()) {
|
||||||
|
output.writeBoolean(true);
|
||||||
|
output.writeString(System.NAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output.writeBoolean(false);
|
||||||
|
Simple simple = (Simple) user;
|
||||||
|
output.writeString(simple.username);
|
||||||
|
output.writeStringArray(simple.roles);
|
||||||
|
}
|
||||||
|
|
||||||
public static class Simple extends User {
|
public static class Simple extends User {
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
|
@ -38,7 +65,7 @@ public abstract class User {
|
||||||
|
|
||||||
public Simple(String username, String... roles) {
|
public Simple(String username, String... roles) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.roles = roles;
|
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -91,5 +118,4 @@ public abstract class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ public interface AuditTrail {
|
||||||
public void anonymousAccess(String action, TransportMessage<?> message) {
|
public void anonymousAccess(String action, TransportMessage<?> message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void anonymousAccess(RestRequest request) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
||||||
}
|
}
|
||||||
|
@ -62,6 +66,8 @@ public interface AuditTrail {
|
||||||
|
|
||||||
void anonymousAccess(String action, TransportMessage<?> message);
|
void anonymousAccess(String action, TransportMessage<?> message);
|
||||||
|
|
||||||
|
void anonymousAccess(RestRequest request);
|
||||||
|
|
||||||
void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message);
|
void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message);
|
||||||
|
|
||||||
void authenticationFailed(AuthenticationToken token, RestRequest request);
|
void authenticationFailed(AuthenticationToken token, RestRequest request);
|
||||||
|
|
|
@ -41,6 +41,13 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void anonymousAccess(RestRequest request) {
|
||||||
|
for (AuditTrail auditTrail : auditTrails) {
|
||||||
|
auditTrail.anonymousAccess(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
||||||
for (AuditTrail auditTrail : auditTrails) {
|
for (AuditTrail auditTrail : auditTrails) {
|
||||||
|
|
|
@ -59,6 +59,15 @@ public class LoggingAuditTrail implements AuditTrail {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void anonymousAccess(RestRequest request) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("ANONYMOUS_ACCESS\thost=[{}], URI=[{}], request=[{}]", request.getRemoteAddress(), request.uri(), request);
|
||||||
|
} else {
|
||||||
|
logger.warn("ANONYMOUS_ACCESS\thost=[{}], URI=[{}]", request.getRemoteAddress(), request.uri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
||||||
String indices = indices(message);
|
String indices = indices(message);
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryModule;
|
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryModule;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
|
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
|
||||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +24,6 @@ public class AuthenticationModule extends AbstractShieldModule.Node.Spawn {
|
||||||
@Override
|
@Override
|
||||||
public Iterable<? extends Node> spawnModules() {
|
public Iterable<? extends Node> spawnModules() {
|
||||||
return ImmutableList.of(
|
return ImmutableList.of(
|
||||||
new SystemRealm.Module(settings),
|
|
||||||
new ESUsersModule(settings),
|
new ESUsersModule(settings),
|
||||||
new LdapModule(settings),
|
new LdapModule(settings),
|
||||||
new ActiveDirectoryModule(settings)
|
new ActiveDirectoryModule(settings)
|
||||||
|
|
|
@ -15,52 +15,45 @@ import org.elasticsearch.transport.TransportMessage;
|
||||||
public interface AuthenticationService {
|
public interface AuthenticationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts an authentication token from the given rest request and if found registers it on
|
* Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e.
|
||||||
* the request. If not found, an {@link AuthenticationException} is thrown.
|
* a user was indeed associated with the request and the credentials were verified to be valid), the method returns
|
||||||
|
* the user and that user is then "attached" to the request's context.
|
||||||
|
*
|
||||||
|
* @param request The request to be authenticated
|
||||||
|
* @return The authenticated user
|
||||||
|
* @throws AuthenticationException If no user was associated with the request or if the associated user credentials
|
||||||
|
* were found to be invalid
|
||||||
*/
|
*/
|
||||||
AuthenticationToken token(RestRequest request) throws AuthenticationException;
|
User authenticate(RestRequest request) throws AuthenticationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the authenticate token from the given message. If no recognized auth token is associated
|
* Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e.
|
||||||
* with the message, an AuthenticationException is thrown.
|
* a user was indeed associated with the request and the credentials were verified to be valid), the method returns
|
||||||
|
* the user and that user is then "attached" to the message's context. If no user was found to be attached to the given
|
||||||
|
* message, the the given fallback user will be returned instead.
|
||||||
|
*
|
||||||
|
* @param action The action of the message
|
||||||
|
* @param message The message to be authenticated
|
||||||
|
* @param fallbackUser The default user that will be assumed if no other user is attached to the message. Can be
|
||||||
|
* {@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 The authenticated user (either the attached one or if there isn't the fallback one if provided)
|
||||||
|
*
|
||||||
|
* @throws AuthenticationException 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.
|
||||||
*/
|
*/
|
||||||
AuthenticationToken token(String action, TransportMessage<?> message);
|
User authenticate(String action, TransportMessage message, User fallbackUser);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the authenticate token from the given message. If no recognized auth token is associated
|
* Checks if there's alreay a user header attached to the given message. If missing, a new header is
|
||||||
* with the message and the given defaultToken is not {@code null}, the default token will be returned.
|
* set on the message with the given user (encoded).
|
||||||
* Otherwise an AuthenticationException is thrown.
|
*
|
||||||
|
* @param message The message
|
||||||
|
* @param user The user to be attached if the header is missing
|
||||||
*/
|
*/
|
||||||
AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken);
|
void attachUserHeaderIfMissing(TransportMessage message, User user);
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticates the user associated with the given request based on the given authentication token.
|
|
||||||
*
|
|
||||||
* On successful authentication, the {@link org.elasticsearch.shield.User user} that is associated
|
|
||||||
* with the request (i.e. that is associated with the token's {@link AuthenticationToken#principal() principal})
|
|
||||||
* will be returned. If authentication fails, an {@link AuthenticationException} will be thrown.
|
|
||||||
*
|
|
||||||
* @param action The executed action
|
|
||||||
* @param message The executed message
|
|
||||||
* @param token The authentication token associated with the given request (must not be {@code null})
|
|
||||||
* @return The authenticated User
|
|
||||||
* @throws AuthenticationException If no user could be authenticated (can either be due to missing
|
|
||||||
* supported authentication token, or simply due to bad credentials.
|
|
||||||
*/
|
|
||||||
User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticates the user associated with the given request based on the given authentication token.
|
|
||||||
*
|
|
||||||
* On successful authentication, the {@link org.elasticsearch.shield.User user} that is associated
|
|
||||||
* with the request (i.e. that is associated with the token's {@link AuthenticationToken#principal() principal})
|
|
||||||
* will be returned. If authentication fails, an {@link AuthenticationException} will be thrown.
|
|
||||||
*
|
|
||||||
* @param request The executed request
|
|
||||||
* @param token The authentication token associated with the given request (must not be {@code null})
|
|
||||||
* @return The authenticated User
|
|
||||||
* @throws AuthenticationException If no user could be authenticated (can either be due to missing
|
|
||||||
* supported authentication token, or simply due to bad credentials.
|
|
||||||
*/
|
|
||||||
User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,21 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc;
|
package org.elasticsearch.shield.authc;
|
||||||
|
|
||||||
import org.elasticsearch.common.ContextHolder;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.inject.internal.Nullable;
|
import org.elasticsearch.common.io.stream.BytesStreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.rest.RestRequest;
|
import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.shield.User;
|
import org.elasticsearch.shield.User;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
|
import org.elasticsearch.shield.key.KeyService;
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
import org.elasticsearch.transport.TransportMessage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An authentication service that delegates the authentication process to its configured {@link Realm realms}.
|
* An authentication service that delegates the authentication process to its configured {@link Realm realms}.
|
||||||
* This service also supports request level caching of authenticated users (i.e. once a user authenticated
|
* This service also supports request level caching of authenticated users (i.e. once a user authenticated
|
||||||
|
@ -22,65 +27,94 @@ import org.elasticsearch.transport.TransportMessage;
|
||||||
*/
|
*/
|
||||||
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {
|
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {
|
||||||
|
|
||||||
static final String TOKEN_CTX_KEY = "_shield_token";
|
static final String TOKEN_KEY = "_shield_token";
|
||||||
static final String USER_CTX_KEY = "_shield_user";
|
static final String USER_KEY = "_shield_user";
|
||||||
|
|
||||||
private final Realm[] realms;
|
private final Realm[] realms;
|
||||||
private final AuditTrail auditTrail;
|
private final AuditTrail auditTrail;
|
||||||
|
private final KeyService keyService;
|
||||||
|
private final boolean signUserHeader;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public InternalAuthenticationService(Settings settings, Realms realms, @Nullable AuditTrail auditTrail) {
|
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, KeyService keyService) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.realms = realms.realms();
|
this.realms = realms.realms();
|
||||||
this.auditTrail = auditTrail;
|
this.auditTrail = auditTrail;
|
||||||
|
this.keyService = keyService;
|
||||||
|
this.signUserHeader = componentSettings.getAsBoolean("sign_user_header", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken token(RestRequest request) throws AuthenticationException {
|
public User authenticate(RestRequest request) throws AuthenticationException {
|
||||||
for (Realm realm : realms) {
|
AuthenticationToken token = token(request);
|
||||||
AuthenticationToken token = realm.token(request);
|
if (token == null) {
|
||||||
if (token != null) {
|
auditTrail.anonymousAccess(request);
|
||||||
request.putInContext(TOKEN_CTX_KEY, token);
|
throw new AuthenticationException("Missing authentication token");
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new AuthenticationException("Missing authentication token");
|
User user = authenticate(request, token);
|
||||||
|
if (user == null) {
|
||||||
|
throw new AuthenticationException("Unable to authenticate user for request");
|
||||||
|
}
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken token(String action, TransportMessage<?> message) {
|
public User authenticate(String action, TransportMessage message, User fallbackUser) {
|
||||||
return token(action, message, null);
|
User user = (User) message.getContext().get(USER_KEY);
|
||||||
|
if (user != null) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
String header = (String) message.getHeader(USER_KEY);
|
||||||
|
if (header != null) {
|
||||||
|
if (signUserHeader) {
|
||||||
|
header = keyService.unsignAndVerify(header);
|
||||||
|
}
|
||||||
|
user = decodeUser(header);
|
||||||
|
}
|
||||||
|
if (user == null) {
|
||||||
|
user = authenticateWithRealms(action, message, fallbackUser);
|
||||||
|
header = signUserHeader ? keyService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
||||||
|
message.putHeader(USER_KEY, header);
|
||||||
|
}
|
||||||
|
message.putInContext(USER_KEY, user);
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
public void attachUserHeaderIfMissing(TransportMessage message, User user) {
|
||||||
public AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken) {
|
String header = (String) message.getHeader(USER_KEY);
|
||||||
AuthenticationToken token = message.getFromContext(TOKEN_CTX_KEY);
|
if (header == null) {
|
||||||
if (token != null) {
|
header = (String) message.getHeader(TOKEN_KEY);
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
for (Realm realm : realms) {
|
if (header == null) {
|
||||||
token = realm.token(message);
|
message.putInContext(USER_KEY, user);
|
||||||
if (token != null) {
|
header = signUserHeader ? keyService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
||||||
|
message.putHeader(USER_KEY, header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
static User decodeUser(String text) {
|
||||||
logger.trace("Realm [{}] resolved auth token [{}] from transport request with action [{}]", realm.type(), token.principal(), action);
|
byte[] bytes = Base64.decodeBase64(text);
|
||||||
}
|
try {
|
||||||
|
BytesStreamInput input = new BytesStreamInput(bytes, true);
|
||||||
|
return User.readFrom(input);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new AuthenticationException("Could not read authenticated user", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message.putInContext(TOKEN_CTX_KEY, token);
|
static String encodeUser(User user, ESLogger logger) {
|
||||||
return token;
|
try {
|
||||||
|
BytesStreamOutput output = new BytesStreamOutput();
|
||||||
|
User.writeTo(user, output);
|
||||||
|
byte[] bytes = output.bytes().toBytes();
|
||||||
|
return Base64.encodeBase64String(bytes);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Could not encode authenticated user in message header... falling back to token headers", ioe);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultToken == null) {
|
|
||||||
if (auditTrail != null) {
|
|
||||||
auditTrail.anonymousAccess(action, message);
|
|
||||||
}
|
|
||||||
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
message.putInContext(TOKEN_CTX_KEY, defaultToken);
|
|
||||||
return defaultToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,63 +126,91 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||||
*
|
*
|
||||||
* The order by which the realms are checked is defined in {@link Realms}.
|
* The order by which the realms are checked is defined in {@link Realms}.
|
||||||
*
|
*
|
||||||
* @param action The executed action
|
* @param action The executed action
|
||||||
* @param message The executed request
|
* @param message The executed request
|
||||||
* @param token The authentication token
|
* @param fallbackUser The user to assume if there is not other user attached to the message
|
||||||
* @return The authenticated user
|
* @return The authenticated user
|
||||||
* @throws AuthenticationException If none of the configured realms successfully authenticated the
|
* @throws AuthenticationException If none of the configured realms successfully authenticated the
|
||||||
* request
|
* request
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException {
|
User authenticateWithRealms(String action, TransportMessage<?> message, User fallbackUser) throws AuthenticationException {
|
||||||
assert token != null : "cannot authenticate null tokens";
|
AuthenticationToken token = token(action, message);
|
||||||
try {
|
|
||||||
User user = (User) message.getContext().get(USER_CTX_KEY);
|
if (token == null) {
|
||||||
if (user != null) {
|
if (fallbackUser == null) {
|
||||||
return user;
|
auditTrail.anonymousAccess(action, message);
|
||||||
|
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
||||||
}
|
}
|
||||||
|
return fallbackUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
for (Realm realm : realms) {
|
for (Realm realm : realms) {
|
||||||
if (realm.supports(token)) {
|
if (realm.supports(token)) {
|
||||||
user = realm.authenticate(token);
|
User user = realm.authenticate(token);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
message.putInContext(USER_CTX_KEY, user);
|
|
||||||
return user;
|
return user;
|
||||||
} else if (auditTrail != null) {
|
|
||||||
auditTrail.authenticationFailed(realm.type(), token, action, message);
|
|
||||||
}
|
}
|
||||||
|
auditTrail.authenticationFailed(realm.type(), token, action, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auditTrail != null) {
|
auditTrail.authenticationFailed(token, action, message);
|
||||||
auditTrail.authenticationFailed(token, action, message);
|
|
||||||
}
|
|
||||||
throw new AuthenticationException("Unable to authenticate user for request");
|
throw new AuthenticationException("Unable to authenticate user for request");
|
||||||
} finally {
|
} finally {
|
||||||
token.clearCredentials();
|
token.clearCredentials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException {
|
||||||
public User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException {
|
|
||||||
assert token != null : "cannot authenticate null tokens";
|
assert token != null : "cannot authenticate null tokens";
|
||||||
try {
|
try {
|
||||||
for (Realm realm : realms) {
|
for (Realm realm : realms) {
|
||||||
if (realm.supports(token)) {
|
if (realm.supports(token)) {
|
||||||
User user = realm.authenticate(token);
|
User user = realm.authenticate(token);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
request.putInContext(USER_CTX_KEY, user);
|
request.putInContext(USER_KEY, user);
|
||||||
return user;
|
return user;
|
||||||
} else if (auditTrail != null) {
|
|
||||||
auditTrail.authenticationFailed(realm.type(), token, request);
|
|
||||||
}
|
}
|
||||||
|
auditTrail.authenticationFailed(realm.type(), token, request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auditTrail != null) {
|
auditTrail.authenticationFailed(token, request);
|
||||||
auditTrail.authenticationFailed(token, request);
|
return null;
|
||||||
}
|
|
||||||
throw new AuthenticationException("Unable to authenticate user for request");
|
|
||||||
} finally {
|
} finally {
|
||||||
token.clearCredentials();
|
token.clearCredentials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthenticationToken token(RestRequest request) throws AuthenticationException {
|
||||||
|
for (Realm realm : realms) {
|
||||||
|
AuthenticationToken token = realm.token(request);
|
||||||
|
if (token != null) {
|
||||||
|
request.putInContext(TOKEN_KEY, token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
AuthenticationToken token(String action, TransportMessage<?> message) {
|
||||||
|
AuthenticationToken token = message.getFromContext(TOKEN_KEY);
|
||||||
|
if (token != null) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
for (Realm realm : realms) {
|
||||||
|
token = realm.token(message);
|
||||||
|
if (token != null) {
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Realm [{}] resolved auth token [{}] from transport request with action [{}]", realm.type(), token.principal(), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.putInContext(TOKEN_KEY, token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.elasticsearch.common.logging.ESLoggerFactory;
|
||||||
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm;
|
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||||
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -27,13 +26,9 @@ public class Realms {
|
||||||
private final Realm[] realms;
|
private final Realm[] realms;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Realms(SystemRealm system,
|
public Realms(@Nullable ESUsersRealm esusers, @Nullable LdapRealm ldap, @Nullable ActiveDirectoryRealm activeDirectory) {
|
||||||
@Nullable ESUsersRealm esusers,
|
|
||||||
@Nullable LdapRealm ldap,
|
|
||||||
@Nullable ActiveDirectoryRealm activeDirectory) {
|
|
||||||
|
|
||||||
List<Realm> realms = new ArrayList<>();
|
List<Realm> realms = new ArrayList<>();
|
||||||
realms.add(system);
|
|
||||||
if (esusers != null) {
|
if (esusers != null) {
|
||||||
logger.info("Realm [" + esusers.type() + "] is used");
|
logger.info("Realm [" + esusers.type() + "] is used");
|
||||||
realms.add(esusers);
|
realms.add(esusers);
|
||||||
|
@ -52,5 +47,4 @@ public class Realms {
|
||||||
Realm[] realms() {
|
Realm[] realms() {
|
||||||
return realms;
|
return realms;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.shield.authc.system;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.rest.RestRequest;
|
|
||||||
import org.elasticsearch.shield.User;
|
|
||||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
|
||||||
import org.elasticsearch.shield.authc.Realm;
|
|
||||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SystemRealm implements Realm<AuthenticationToken> {
|
|
||||||
|
|
||||||
public static final AuthenticationToken TOKEN = new AuthenticationToken() {
|
|
||||||
@Override
|
|
||||||
public String principal() {
|
|
||||||
return "_system";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object credentials() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearCredentials() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String type() {
|
|
||||||
return "system";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationToken token(RestRequest request) {
|
|
||||||
return null; // system token can never come from the rest API
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationToken token(TransportMessage<?> message) {
|
|
||||||
// as far as this realm is concerned, there's never a system token
|
|
||||||
// in the request. The decision of whether a request is a system
|
|
||||||
// request or not, is made elsewhere where the system token is
|
|
||||||
// assumed
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(AuthenticationToken token) {
|
|
||||||
return token == TOKEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User authenticate(AuthenticationToken token) {
|
|
||||||
return token == TOKEN ? User.SYSTEM : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Module extends AbstractShieldModule.Node {
|
|
||||||
|
|
||||||
public Module(Settings settings) {
|
|
||||||
super(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configureNode() {
|
|
||||||
bind(SystemRealm.class).asEagerSingleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -41,7 +41,7 @@ public class InternalKeyService extends AbstractComponent implements KeyService
|
||||||
static final String FILE_NAME = "system_key";
|
static final String FILE_NAME = "system_key";
|
||||||
static final String HMAC_ALGO = "HmacSHA1";
|
static final String HMAC_ALGO = "HmacSHA1";
|
||||||
|
|
||||||
private static final Pattern SIG_PATTERN = Pattern.compile("\\$\\$[0-9]+\\$\\$.+");
|
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$.+");
|
||||||
|
|
||||||
private final Path keyFile;
|
private final Path keyFile;
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ public class SystemKeyTool extends CliTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Generate extends Command {
|
static class Generate extends Command {
|
||||||
private static final String NAME = "generate";
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Generate.class).build();
|
private static final CliToolConfig.Cmd CMD = cmd("generate", Generate.class).build();
|
||||||
|
|
||||||
final Path path;
|
final Path path;
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ import java.util.Arrays;
|
||||||
*/
|
*/
|
||||||
public class SSLService extends AbstractComponent {
|
public class SSLService extends AbstractComponent {
|
||||||
|
|
||||||
static final String[] DEFAULT_CIPHERS = new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"};
|
static final String[] DEFAULT_CIPHERS = new String[]{ "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" };
|
||||||
public static final String SHIELD_TRANSPORT_SSL = "shield.transport.ssl";
|
public static final String SHIELD_TRANSPORT_SSL = "shield.transport.ssl";
|
||||||
public static final String SHIELD_HTTP_SSL = "shield.http.ssl";
|
public static final String SHIELD_HTTP_SSL = "shield.http.ssl";
|
||||||
public static final String SHIELD_AUTHC_LDAP_URL = "shield.authc.ldap.url";
|
public static final String SHIELD_AUTHC_LDAP_URL = "shield.authc.ldap.url";
|
||||||
|
|
||||||
private final TrustManagerFactory trustFactory;
|
private final TrustManagerFactory trustFactory;
|
||||||
private final SSLContext sslContext;
|
|
||||||
private final String[] ciphers;
|
|
||||||
private final KeyManagerFactory keyManagerFactory;
|
private final KeyManagerFactory keyManagerFactory;
|
||||||
private final String sslProtocol;
|
private final String sslProtocol;
|
||||||
|
private final SSLContext sslContext;
|
||||||
|
private final String[] ciphers;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SSLService(Settings settings) {
|
public SSLService(Settings settings) {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.shield.transport;
|
||||||
|
|
||||||
|
import org.elasticsearch.transport.TransportRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface ClientTransportFilter {
|
||||||
|
|
||||||
|
static final ClientTransportFilter NOOP = new ClientTransportFilter() {
|
||||||
|
@Override
|
||||||
|
public void outbound(String action, TransportRequest request) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called just before the given request is sent by the transport. Any exception
|
||||||
|
* thrown by this method will stop the request from being sent and the error will
|
||||||
|
* be sent back to the sender.
|
||||||
|
*/
|
||||||
|
void outbound(String action, TransportRequest request);
|
||||||
|
|
||||||
|
}
|
|
@ -51,10 +51,12 @@ public class SecuredTransportModule extends AbstractShieldModule.Spawn implement
|
||||||
if (clientMode) {
|
if (clientMode) {
|
||||||
// no ip filtering on the client
|
// no ip filtering on the client
|
||||||
bind(ServerTransportFilter.class).toInstance(ServerTransportFilter.NOOP);
|
bind(ServerTransportFilter.class).toInstance(ServerTransportFilter.NOOP);
|
||||||
|
bind(ClientTransportFilter.class).toInstance(ClientTransportFilter.NOOP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bind(ServerTransportFilter.class).to(SecurityFilter.ServerTransport.class).asEagerSingleton();
|
bind(ServerTransportFilter.class).to(SecurityFilter.ServerTransport.class).asEagerSingleton();
|
||||||
|
bind(ClientTransportFilter.class).to(SecurityFilter.ClientTransport.class).asEagerSingleton();
|
||||||
if (settings.getAsBoolean("shield.transport.n2n.ip_filter.enabled", true)) {
|
if (settings.getAsBoolean("shield.transport.n2n.ip_filter.enabled", true)) {
|
||||||
bind(IPFilteringN2NAuthenticator.class).asEagerSingleton();
|
bind(IPFilteringN2NAuthenticator.class).asEagerSingleton();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.transport;
|
package org.elasticsearch.shield.transport;
|
||||||
|
|
||||||
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
@ -15,17 +16,29 @@ import org.elasticsearch.transport.*;
|
||||||
*/
|
*/
|
||||||
public class SecuredTransportService extends TransportService {
|
public class SecuredTransportService extends TransportService {
|
||||||
|
|
||||||
private final ServerTransportFilter filter;
|
private final ServerTransportFilter serverFilter;
|
||||||
|
private final ClientTransportFilter clientFilter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SecuredTransportService(Settings settings, Transport transport, ThreadPool threadPool, ServerTransportFilter filter) {
|
public SecuredTransportService(Settings settings, Transport transport, ThreadPool threadPool, ServerTransportFilter serverFilter, ClientTransportFilter clientFilter) {
|
||||||
super(settings, transport, threadPool);
|
super(settings, transport, threadPool);
|
||||||
this.filter = filter;
|
this.serverFilter = serverFilter;
|
||||||
|
this.clientFilter = clientFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerHandler(String action, TransportRequestHandler handler) {
|
public void registerHandler(String action, TransportRequestHandler handler) {
|
||||||
super.registerHandler(action, new SecuredRequestHandler(action, handler, filter));
|
super.registerHandler(action, new SecuredRequestHandler(action, handler, serverFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends TransportResponse> void sendRequest(DiscoveryNode node, String action, TransportRequest request, TransportRequestOptions options, TransportResponseHandler<T> handler) {
|
||||||
|
try {
|
||||||
|
clientFilter.outbound(action, request);
|
||||||
|
super.sendRequest(node, action, request, options, handler);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
handler.handleException(new TransportException("failed sending request", t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SecuredRequestHandler implements TransportRequestHandler {
|
static class SecuredRequestHandler implements TransportRequestHandler {
|
||||||
|
|
|
@ -12,9 +12,11 @@ import org.elasticsearch.cluster.ClusterService;
|
||||||
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.transport.TransportAddress;
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
|
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||||
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -39,16 +41,23 @@ public class PermissionPrecedenceTests extends ShieldIntegrationTest {
|
||||||
" indices:\n" +
|
" indices:\n" +
|
||||||
" '*': all\n" +
|
" '*': all\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
"transport_client:\n" +
|
||||||
|
" cluster:\n" +
|
||||||
|
" - cluster:monitor/nodes/info\n" +
|
||||||
|
" - cluster:monitor/state\n" +
|
||||||
|
"\n" +
|
||||||
"user:\n" +
|
"user:\n" +
|
||||||
" indices:\n" +
|
" indices:\n" +
|
||||||
" 'test_*': all\n";
|
" 'test_*': all\n";
|
||||||
|
|
||||||
static final String USERS =
|
static final String USERS =
|
||||||
"admin:{plain}test123\n" +
|
"admin:{plain}test123\n" +
|
||||||
|
"client:{plain}test123\n" +
|
||||||
"user:{plain}test123\n";
|
"user:{plain}test123\n";
|
||||||
|
|
||||||
static final String USERS_ROLES =
|
static final String USERS_ROLES =
|
||||||
"admin:admin\n" +
|
"admin:admin\n" +
|
||||||
|
"transport_client:client\n" +
|
||||||
"user:user\n";
|
"user:user\n";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -83,6 +92,7 @@ public class PermissionPrecedenceTests extends ShieldIntegrationTest {
|
||||||
TransportAddress address = clusterService.localNode().address();
|
TransportAddress address = clusterService.localNode().address();
|
||||||
|
|
||||||
try (TransportClient client = new TransportClient(ImmutableSettings.builder()
|
try (TransportClient client = new TransportClient(ImmutableSettings.builder()
|
||||||
|
.put("shield.user", "client:test123")
|
||||||
.put("cluster.name", internalCluster().getClusterName())
|
.put("cluster.name", internalCluster().getClusterName())
|
||||||
.put("node.mode", "network")
|
.put("node.mode", "network")
|
||||||
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")))
|
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")))
|
||||||
|
|
|
@ -17,8 +17,6 @@ import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
import org.elasticsearch.shield.authc.AuthenticationException;
|
import org.elasticsearch.shield.authc.AuthenticationException;
|
||||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
|
||||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||||
import org.elasticsearch.shield.key.KeyService;
|
import org.elasticsearch.shield.key.KeyService;
|
||||||
|
@ -59,36 +57,30 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess() throws Exception {
|
public void testAuthenticateAndAuthorize() throws Exception {
|
||||||
TransportRequest request = new InternalRequest();
|
TransportRequest request = new InternalRequest();
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
||||||
User user = new User.Simple("_username", "r1");
|
User user = new User.Simple("_username", "r1");
|
||||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
filter.authenticateAndAuthorize("_action", request, null);
|
||||||
filter.authenticateAndAuthorize("_action", request);
|
|
||||||
verify(authzService).authorize(user, "_action", request);
|
verify(authzService).authorize(user, "_action", request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess_InternalAction() throws Exception {
|
public void testAuthenticateAndAuthorize_InternalAction() throws Exception {
|
||||||
TransportRequest request = new InternalRequest();
|
TransportRequest request = new InternalRequest();
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
||||||
User user = new User.Simple("_username", "r1");
|
User user = new User.Simple("_username", "r1");
|
||||||
when(authcService.token("internal:_action", request, SystemRealm.TOKEN)).thenReturn(token);
|
when(authcService.authenticate("internal:_action", request, User.SYSTEM)).thenReturn(user);
|
||||||
when(authcService.authenticate("internal:_action", request, token)).thenReturn(user);
|
filter.authenticateAndAuthorize("internal:_action", request, User.SYSTEM);
|
||||||
filter.authenticateAndAuthorize("internal:_action", request);
|
|
||||||
verify(authzService).authorize(user, "internal:_action", request);
|
verify(authzService).authorize(user, "internal:_action", request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess_AuthenticationFails_Authenticate() throws Exception {
|
public void testAuthenticateAndAuthorize_AuthenticationFails_Authenticate() throws Exception {
|
||||||
thrown.expect(AuthenticationException.class);
|
thrown.expect(AuthenticationException.class);
|
||||||
thrown.expectMessage("failed authc");
|
thrown.expectMessage("failed authc");
|
||||||
TransportRequest request = new InternalRequest();
|
TransportRequest request = new InternalRequest();
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
filter.authenticateAndAuthorize("_action", request, null);
|
||||||
when(authcService.authenticate("_action", request, token)).thenThrow(new AuthenticationException("failed authc"));
|
|
||||||
filter.authenticateAndAuthorize("_action", request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -96,42 +88,38 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
thrown.expect(AuthenticationException.class);
|
thrown.expect(AuthenticationException.class);
|
||||||
thrown.expectMessage("failed authc");
|
thrown.expectMessage("failed authc");
|
||||||
RestRequest request = mock(RestRequest.class);
|
RestRequest request = mock(RestRequest.class);
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc"));
|
||||||
when(authcService.token(request)).thenReturn(token);
|
|
||||||
when(authcService.authenticate(request, token)).thenThrow(new AuthenticationException("failed authc"));
|
|
||||||
filter.authenticate(request);
|
filter.authenticate(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess_AuthenticationFails_NoToken() throws Exception {
|
public void testAuthenticateAndAuthorize_AuthenticationFails_NoToken() throws Exception {
|
||||||
thrown.expect(AuthenticationException.class);
|
thrown.expect(AuthenticationException.class);
|
||||||
thrown.expectMessage("failed authc");
|
thrown.expectMessage("failed authc");
|
||||||
TransportRequest request = new InternalRequest();
|
TransportRequest request = new InternalRequest();
|
||||||
when(authcService.token("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||||
filter.authenticateAndAuthorize("_action", request);
|
filter.authenticateAndAuthorize("_action", request, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess_Rest_AuthenticationFails_NoToken() throws Exception {
|
public void testAuthenticateAndAuthorize_Rest_AuthenticationFails_NoToken() throws Exception {
|
||||||
thrown.expect(AuthenticationException.class);
|
thrown.expect(AuthenticationException.class);
|
||||||
thrown.expectMessage("failed authc");
|
thrown.expectMessage("failed authc");
|
||||||
RestRequest request = mock(RestRequest.class);
|
RestRequest request = mock(RestRequest.class);
|
||||||
when(authcService.token(request)).thenThrow(new AuthenticationException("failed authc"));
|
when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc"));
|
||||||
filter.authenticate(request);
|
filter.authenticate(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess_AuthorizationFails() throws Exception {
|
public void testAuthenticateAndAuthorize_AuthorizationFails() throws Exception {
|
||||||
thrown.expect(AuthorizationException.class);
|
thrown.expect(AuthorizationException.class);
|
||||||
thrown.expectMessage("failed authz");
|
thrown.expectMessage("failed authz");
|
||||||
TransportRequest request = new InternalRequest();
|
TransportRequest request = new InternalRequest();
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
||||||
User user = new User.Simple("_username", "r1");
|
User user = new User.Simple("_username", "r1");
|
||||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
|
||||||
doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request);
|
doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request);
|
||||||
filter.authenticateAndAuthorize("_action", request);
|
filter.authenticateAndAuthorize("_action", request, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -140,7 +128,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
||||||
InternalRequest request = new InternalRequest();
|
InternalRequest request = new InternalRequest();
|
||||||
transport.inbound("_action", request);
|
transport.inbound("_action", request);
|
||||||
verify(filter).authenticateAndAuthorize("_action", request);
|
verify(filter).authenticateAndAuthorize("_action", request, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -150,7 +138,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
filter = mock(SecurityFilter.class);
|
filter = mock(SecurityFilter.class);
|
||||||
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
||||||
InternalRequest request = new InternalRequest();
|
InternalRequest request = new InternalRequest();
|
||||||
doThrow(new RuntimeException("process-error")).when(filter).authenticateAndAuthorize("_action", request);
|
doThrow(new RuntimeException("process-error")).when(filter).authenticateAndAuthorize("_action", request, null);
|
||||||
transport.inbound("_action", request);
|
transport.inbound("_action", request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +151,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||||
when(filter.unsign(any(User.class), eq("_action"), eq(request))).thenReturn(request);
|
when(filter.unsign(any(User.class), eq("_action"), eq(request))).thenReturn(request);
|
||||||
action.apply("_action", request, listener, chain);
|
action.apply("_action", request, listener, chain);
|
||||||
verify(filter).authenticateAndAuthorize("_action", request);
|
verify(filter).authenticateAndAuthorize("_action", request, User.SYSTEM);
|
||||||
verify(chain).proceed(eq("_action"), eq(request), isA(SecurityFilter.SigningListener.class));
|
verify(chain).proceed(eq("_action"), eq(request), isA(SecurityFilter.SigningListener.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +163,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
ActionListener listener = mock(ActionListener.class);
|
ActionListener listener = mock(ActionListener.class);
|
||||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||||
RuntimeException exception = new RuntimeException("process-error");
|
RuntimeException exception = new RuntimeException("process-error");
|
||||||
doThrow(exception).when(filter).authenticateAndAuthorize("_action", request);
|
doThrow(exception).when(filter).authenticateAndAuthorize("_action", request, User.SYSTEM);
|
||||||
action.apply("_action", request, listener, chain);
|
action.apply("_action", request, listener, chain);
|
||||||
verify(listener).onFailure(exception);
|
verify(listener).onFailure(exception);
|
||||||
verifyNoMoreInteractions(chain);
|
verifyNoMoreInteractions(chain);
|
||||||
|
@ -189,9 +177,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||||
SignatureException sigException = new SignatureException("bad bad boy");
|
SignatureException sigException = new SignatureException("bad bad boy");
|
||||||
User user = mock(User.class);
|
User user = mock(User.class);
|
||||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
|
||||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
|
||||||
when(keyService.signed("scroll_id")).thenReturn(true);
|
when(keyService.signed("scroll_id")).thenReturn(true);
|
||||||
doThrow(sigException).when(keyService).unsignAndVerify("scroll_id");
|
doThrow(sigException).when(keyService).unsignAndVerify("scroll_id");
|
||||||
action.apply("_action", request, listener, chain);
|
action.apply("_action", request, listener, chain);
|
||||||
|
@ -207,7 +193,6 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
RestChannel channel = mock(RestChannel.class);
|
RestChannel channel = mock(RestChannel.class);
|
||||||
RestFilterChain chain = mock(RestFilterChain.class);
|
RestFilterChain chain = mock(RestFilterChain.class);
|
||||||
rest.process(request, channel, chain);
|
rest.process(request, channel, chain);
|
||||||
verify(authcService).token(request);
|
|
||||||
verify(restController).registerFilter(rest);
|
verify(restController).registerFilter(rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +205,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
||||||
RestRequest request = mock(RestRequest.class);
|
RestRequest request = mock(RestRequest.class);
|
||||||
RestChannel channel = mock(RestChannel.class);
|
RestChannel channel = mock(RestChannel.class);
|
||||||
RestFilterChain chain = mock(RestFilterChain.class);
|
RestFilterChain chain = mock(RestFilterChain.class);
|
||||||
doThrow(exception).when(authcService).token(request);
|
doThrow(exception).when(authcService).authenticate(request);
|
||||||
rest.process(request, channel, chain);
|
rest.process(request, channel, chain);
|
||||||
verify(restController).registerFilter(rest);
|
verify(restController).registerFilter(rest);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import static org.mockito.Mockito.when;
|
||||||
public class LoggingAuditTrailTests extends ElasticsearchTestCase {
|
public class LoggingAuditTrailTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAnonymousAccess() throws Exception {
|
public void testAnonymousAccess_Transport() throws Exception {
|
||||||
for (Level level : Level.values()) {
|
for (Level level : Level.values()) {
|
||||||
CapturingLogger logger = new CapturingLogger(level);
|
CapturingLogger logger = new CapturingLogger(level);
|
||||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
|
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
|
||||||
|
@ -59,6 +59,32 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAnonymousAccess_Rest() throws Exception {
|
||||||
|
RestRequest request = mock(RestRequest.class);
|
||||||
|
when(request.getRemoteAddress()).thenReturn(new InetSocketAddress("_hostname", 9200));
|
||||||
|
when(request.uri()).thenReturn("_uri");
|
||||||
|
when(request.toString()).thenReturn("rest_request");
|
||||||
|
|
||||||
|
for (Level level : Level.values()) {
|
||||||
|
CapturingLogger logger = new CapturingLogger(level);
|
||||||
|
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
|
||||||
|
auditTrail.anonymousAccess(request);
|
||||||
|
switch (level) {
|
||||||
|
case ERROR:
|
||||||
|
assertEmptyLog(logger);
|
||||||
|
break;
|
||||||
|
case WARN:
|
||||||
|
case INFO:
|
||||||
|
assertMsg(logger, Level.WARN, "ANONYMOUS_ACCESS\thost=[_hostname:9200], URI=[_uri]");
|
||||||
|
break;
|
||||||
|
case DEBUG:
|
||||||
|
case TRACE:
|
||||||
|
assertMsg(logger, Level.DEBUG, "ANONYMOUS_ACCESS\thost=[_hostname:9200], URI=[_uri], request=[rest_request]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAuthenticationFailed() throws Exception {
|
public void testAuthenticationFailed() throws Exception {
|
||||||
for (Level level : Level.values()) {
|
for (Level level : Level.values()) {
|
||||||
|
|
|
@ -7,10 +7,14 @@ package org.elasticsearch.shield.authc;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.io.stream.BytesStreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.rest.RestRequest;
|
import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.shield.User;
|
import org.elasticsearch.shield.User;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
|
import org.elasticsearch.shield.key.KeyService;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
import org.elasticsearch.transport.TransportMessage;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -20,7 +24,6 @@ import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ -36,10 +39,12 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
InternalAuthenticationService service;
|
InternalAuthenticationService service;
|
||||||
TransportMessage message;
|
TransportMessage message;
|
||||||
RestRequest restRequest;
|
RestRequest restRequest;
|
||||||
|
Realms realms;
|
||||||
Realm firstRealm;
|
Realm firstRealm;
|
||||||
Realm secondRealm;
|
Realm secondRealm;
|
||||||
AuditTrail auditTrail;
|
AuditTrail auditTrail;
|
||||||
AuthenticationToken token;
|
AuthenticationToken token;
|
||||||
|
KeyService keyService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
@ -50,11 +55,12 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.type()).thenReturn("first");
|
when(firstRealm.type()).thenReturn("first");
|
||||||
secondRealm = mock(Realm.class);
|
secondRealm = mock(Realm.class);
|
||||||
when(secondRealm.type()).thenReturn("second");
|
when(secondRealm.type()).thenReturn("second");
|
||||||
Realms realms = mock(Realms.class);
|
realms = mock(Realms.class);
|
||||||
when(realms.realms()).thenReturn(new Realm[] {firstRealm, secondRealm});
|
when(realms.realms()).thenReturn(new Realm[] {firstRealm, secondRealm});
|
||||||
|
keyService = mock(KeyService.class);
|
||||||
|
|
||||||
auditTrail = mock(AuditTrail.class);
|
auditTrail = mock(AuditTrail.class);
|
||||||
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail);
|
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, keyService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
@ -70,52 +76,23 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToken_Missing() throws Exception {
|
public void testToken_Missing() throws Exception {
|
||||||
try {
|
AuthenticationToken token = service.token("_action", message);
|
||||||
service.token("_action", message);
|
assertThat(token, nullValue());
|
||||||
fail("expected authentication exception with missing auth token");
|
|
||||||
} catch (AuthenticationException ae) {
|
|
||||||
assertThat(ae.getMessage(), equalTo("Missing authentication token for request [_action]"));
|
|
||||||
assertContainsWWWAuthenticateHeader(ae);
|
|
||||||
}
|
|
||||||
verify(auditTrail).anonymousAccess("_action", message);
|
|
||||||
verifyNoMoreInteractions(auditTrail);
|
verifyNoMoreInteractions(auditTrail);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), nullValue());
|
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), nullValue());
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testToken_MissingWithNullDefault() throws Exception {
|
|
||||||
try {
|
|
||||||
service.token("_action", message, null);
|
|
||||||
fail("expected authentication exception with missing auth token and null default token");
|
|
||||||
} catch (AuthenticationException ae) {
|
|
||||||
assertThat(ae.getMessage(), equalTo("Missing authentication token for request [_action]"));
|
|
||||||
}
|
|
||||||
verify(auditTrail).anonymousAccess("_action", message);
|
|
||||||
verifyNoMoreInteractions(auditTrail);
|
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testToken_MissingWithDefault() throws Exception {
|
|
||||||
AuthenticationToken result = service.token("_action", message, token);
|
|
||||||
assertThat(result, notNullValue());
|
|
||||||
assertThat(result, is(token));
|
|
||||||
verifyZeroInteractions(auditTrail);
|
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), notNullValue());
|
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), is((Object) token));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
public void testToken_Cached() throws Exception {
|
public void testToken_Cached() throws Exception {
|
||||||
message.putInContext(InternalAuthenticationService.TOKEN_CTX_KEY, token);
|
message.putInContext(InternalAuthenticationService.TOKEN_KEY, token);
|
||||||
AuthenticationToken result = service.token("_action", message, token);
|
AuthenticationToken result = service.token("_action", message);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result, is(token));
|
assertThat(result, is(token));
|
||||||
verifyZeroInteractions(auditTrail);
|
verifyZeroInteractions(auditTrail);
|
||||||
verifyZeroInteractions(firstRealm);
|
verifyZeroInteractions(firstRealm);
|
||||||
verifyZeroInteractions(secondRealm);
|
verifyZeroInteractions(secondRealm);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), notNullValue());
|
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), notNullValue());
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), is((Object) token));
|
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), is((Object) token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
@ -126,12 +103,18 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(secondRealm.supports(token)).thenReturn(true);
|
when(secondRealm.supports(token)).thenReturn(true);
|
||||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||||
|
|
||||||
User result = service.authenticate("_action", message, token);
|
service = spy(service);
|
||||||
|
doReturn(token).when(service).token("_action", message);
|
||||||
|
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
||||||
|
|
||||||
|
User result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result, is(user));
|
assertThat(result, is(user));
|
||||||
verify(auditTrail).authenticationFailed("first", token, "_action", message);
|
verify(auditTrail).authenticationFailed("first", token, "_action", message);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
@ -141,27 +124,34 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(secondRealm.supports(token)).thenReturn(true);
|
when(secondRealm.supports(token)).thenReturn(true);
|
||||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||||
|
|
||||||
User result = service.authenticate("_action", message, token);
|
service = spy(service);
|
||||||
|
doReturn(token).when(service).token("_action", message);
|
||||||
|
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
||||||
|
|
||||||
|
User result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result, is(user));
|
assertThat(result, is(user));
|
||||||
verifyZeroInteractions(auditTrail);
|
verifyZeroInteractions(auditTrail);
|
||||||
verify(firstRealm, never()).authenticate(token);
|
verify(firstRealm, never()).authenticate(token);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
public void testAuthenticate_Cached() throws Exception {
|
public void testAuthenticate_Cached() throws Exception {
|
||||||
User user = new User.Simple("_username", "r1");
|
User user = new User.Simple("_username", "r1");
|
||||||
message.putInContext(InternalAuthenticationService.USER_CTX_KEY, user);
|
message.putInContext(InternalAuthenticationService.USER_KEY, user);
|
||||||
User result = service.authenticate("_action", message, token);
|
User result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result, is(user));
|
assertThat(result, is(user));
|
||||||
verifyZeroInteractions(auditTrail);
|
verifyZeroInteractions(auditTrail);
|
||||||
verifyZeroInteractions(firstRealm);
|
verifyZeroInteractions(firstRealm);
|
||||||
verifyZeroInteractions(secondRealm);
|
verifyZeroInteractions(secondRealm);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
verifyZeroInteractions(keyService);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
||||||
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -171,16 +161,222 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(secondRealm.token(restRequest)).thenReturn(token);
|
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||||
AuthenticationToken foundToken = service.token(restRequest);
|
AuthenticationToken foundToken = service.token(restRequest);
|
||||||
assertThat(foundToken, is(token));
|
assertThat(foundToken, is(token));
|
||||||
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_CTX_KEY), equalTo((Object) token));
|
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_KEY), equalTo((Object) token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToken_Rest_Missing() throws Exception {
|
public void testToken_Rest_Missing() throws Exception {
|
||||||
thrown.expect(AuthenticationException.class);
|
|
||||||
thrown.expectMessage("Missing authentication token");
|
|
||||||
when(firstRealm.token(restRequest)).thenReturn(null);
|
when(firstRealm.token(restRequest)).thenReturn(null);
|
||||||
when(secondRealm.token(restRequest)).thenReturn(null);
|
when(secondRealm.token(restRequest)).thenReturn(null);
|
||||||
service.token(restRequest);
|
AuthenticationToken token = service.token(restRequest);
|
||||||
|
assertThat(token, nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeDecodeUser() throws Exception {
|
||||||
|
User user = new User.Simple("username", "r1", "r2", "r3");
|
||||||
|
String text = InternalAuthenticationService.encodeUser(user, null);
|
||||||
|
User user2 = InternalAuthenticationService.decodeUser(text);
|
||||||
|
assertThat(user, equalTo(user2));
|
||||||
|
|
||||||
|
text = InternalAuthenticationService.encodeUser(User.SYSTEM, null);
|
||||||
|
user2 = InternalAuthenticationService.decodeUser(text);
|
||||||
|
assertThat(User.SYSTEM, sameInstance(user2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserHeader() throws Exception {
|
||||||
|
User user = new User.Simple("_username", "r1");
|
||||||
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
|
service = spy(service);
|
||||||
|
doReturn(token).when(service).token("_action", message);
|
||||||
|
User result = service.authenticate("_action", message, null);
|
||||||
|
assertThat(result, notNullValue());
|
||||||
|
assertThat(result, is(user));
|
||||||
|
String userStr = (String) message.getHeader(InternalAuthenticationService.USER_KEY);
|
||||||
|
assertThat(userStr, notNullValue());
|
||||||
|
assertThat(userStr, equalTo("_signed_user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Transport_Anonymous() throws Exception {
|
||||||
|
when(firstRealm.token(message)).thenReturn(null);
|
||||||
|
when(secondRealm.token(message)).thenReturn(null);
|
||||||
|
try {
|
||||||
|
service.authenticate("_action", message, null);
|
||||||
|
fail("expected an authentication exception when trying to authenticate an anonymous message");
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
verify(auditTrail).anonymousAccess("_action", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Rest_Anonymous() throws Exception {
|
||||||
|
when(firstRealm.token(restRequest)).thenReturn(null);
|
||||||
|
when(secondRealm.token(restRequest)).thenReturn(null);
|
||||||
|
try {
|
||||||
|
service.authenticate(restRequest);
|
||||||
|
fail("expected an authentication exception when trying to authenticate an anonymous message");
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
verify(auditTrail).anonymousAccess(restRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Transport_Fallback() throws Exception {
|
||||||
|
when(firstRealm.token(message)).thenReturn(null);
|
||||||
|
when(secondRealm.token(message)).thenReturn(null);
|
||||||
|
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
|
User user2 = service.authenticate("_action", message, user1);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Transport_Success_NoFallback() throws Exception {
|
||||||
|
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
|
User user2 = service.authenticate("_action", message, null);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Transport_Success_WithFallback() throws Exception {
|
||||||
|
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
|
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticate_Rest_Success() throws Exception {
|
||||||
|
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(firstRealm.token(restRequest)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
|
User user2 = service.authenticate(restRequest);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(restRequest.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutheticate_Transport_ContextAndHeader() throws Exception {
|
||||||
|
User user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
|
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
reset(firstRealm);
|
||||||
|
|
||||||
|
// checking authentication from the context
|
||||||
|
InternalMessage message1 = new InternalMessage();
|
||||||
|
message1.copyContextFrom(message);
|
||||||
|
User user = service.authenticate("_action", message1, User.SYSTEM);
|
||||||
|
assertThat(user, sameInstance(user1));
|
||||||
|
verifyZeroInteractions(firstRealm);
|
||||||
|
reset(firstRealm);
|
||||||
|
|
||||||
|
|
||||||
|
// checking authentication from the user header
|
||||||
|
message1.putHeader(InternalAuthenticationService.USER_KEY, message.getHeader(InternalAuthenticationService.USER_KEY));
|
||||||
|
when(keyService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
|
||||||
|
BytesStreamOutput output = new BytesStreamOutput();
|
||||||
|
message1.writeTo(output);
|
||||||
|
BytesStreamInput input = new BytesStreamInput(output.bytes());
|
||||||
|
InternalMessage message2 = new InternalMessage();
|
||||||
|
message2.readFrom(input);
|
||||||
|
user = service.authenticate("_action", message2, User.SYSTEM);
|
||||||
|
assertThat(user, equalTo(user1));
|
||||||
|
verifyZeroInteractions(firstRealm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutheticate_Transport_ContextAndHeader_NoSigning() throws Exception {
|
||||||
|
Settings settings = ImmutableSettings.builder().put("shield.authc.sign_user_header", false).build();
|
||||||
|
service = new InternalAuthenticationService(settings, realms, auditTrail, keyService);
|
||||||
|
|
||||||
|
User user1 = new User.Simple("username", "r1", "r2");
|
||||||
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
|
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
||||||
|
assertThat(user1, sameInstance(user2));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) InternalAuthenticationService.encodeUser(user1, null)));
|
||||||
|
reset(firstRealm);
|
||||||
|
|
||||||
|
// checking authentication from the context
|
||||||
|
InternalMessage message1 = new InternalMessage();
|
||||||
|
message1.copyContextFrom(message);
|
||||||
|
User user = service.authenticate("_action", message1, User.SYSTEM);
|
||||||
|
assertThat(user, sameInstance(user1));
|
||||||
|
verifyZeroInteractions(firstRealm);
|
||||||
|
reset(firstRealm);
|
||||||
|
|
||||||
|
|
||||||
|
// checking authentication from the user header
|
||||||
|
message1.putHeader(InternalAuthenticationService.USER_KEY, message.getHeader(InternalAuthenticationService.USER_KEY));
|
||||||
|
BytesStreamOutput output = new BytesStreamOutput();
|
||||||
|
message1.writeTo(output);
|
||||||
|
BytesStreamInput input = new BytesStreamInput(output.bytes());
|
||||||
|
InternalMessage message2 = new InternalMessage();
|
||||||
|
message2.readFrom(input);
|
||||||
|
user = service.authenticate("_action", message2, User.SYSTEM);
|
||||||
|
assertThat(user, equalTo(user1));
|
||||||
|
verifyZeroInteractions(firstRealm);
|
||||||
|
|
||||||
|
verifyZeroInteractions(keyService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttachIfMissing_Missing() throws Exception {
|
||||||
|
User user = new User.Simple("username", "r1", "r2");
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
|
service.attachUserHeaderIfMissing(message, user);
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
|
||||||
|
user = User.SYSTEM;
|
||||||
|
message = new InternalMessage();
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
|
when(keyService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
|
service.attachUserHeaderIfMissing(message, user);
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttachIfMissing_Exists() throws Exception {
|
||||||
|
User user = new User.Simple("username", "r1", "r2");
|
||||||
|
message.putInContext(InternalAuthenticationService.USER_KEY, user);
|
||||||
|
message.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user");
|
||||||
|
service.attachUserHeaderIfMissing(message, new User.Simple("username2", "r3", "r4"));
|
||||||
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class InternalMessage extends TransportMessage<InternalMessage> {
|
private static class InternalMessage extends TransportMessage<InternalMessage> {
|
||||||
|
|
|
@ -14,7 +14,8 @@ import org.junit.Test;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
public class CachingUsernamePasswordRealmTests extends ElasticsearchTestCase {
|
public class CachingUsernamePasswordRealmTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@ package org.elasticsearch.shield.authc.support;
|
||||||
|
|
||||||
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
|
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class SecuredStringTests {
|
public class SecuredStringTests {
|
||||||
public static SecuredString build(String password){
|
public static SecuredString build(String password){
|
||||||
|
|
|
@ -195,7 +195,7 @@ public class IndicesResolverIntegrationTests extends ShieldIntegrationTest {
|
||||||
actionRequestBuilder.get();
|
actionRequestBuilder.get();
|
||||||
fail("search should fail due to attempt to access non authorized indices");
|
fail("search should fail due to attempt to access non authorized indices");
|
||||||
} catch(AuthorizationException e) {
|
} catch(AuthorizationException e) {
|
||||||
assertThat(e.getMessage(), containsString("is unauthorized for user [test_user]"));
|
assertThat(e.getMessage(), containsString("is unauthorized for user [test_trans_client_user]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
||||||
import org.elasticsearch.common.transport.TransportAddress;
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
import org.elasticsearch.plugins.PluginsService;
|
import org.elasticsearch.plugins.PluginsService;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
|
@ -34,6 +35,7 @@ import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||||
|
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER;
|
||||||
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||||
|
@ -51,13 +53,27 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
||||||
protected static final String DEFAULT_PASSWORD = "changeme";
|
protected static final String DEFAULT_PASSWORD = "changeme";
|
||||||
protected static final String DEFAULT_ROLE = "user";
|
protected static final String DEFAULT_ROLE = "user";
|
||||||
|
|
||||||
|
protected static final String DEFAULT_TRANSPORT_CLIENT_ROLE = "trans_client_user";
|
||||||
|
protected static final String DEFAULT_TRANSPORT_CLIENT_USER_NAME = "test_trans_client_user";
|
||||||
|
|
||||||
public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n";
|
public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n";
|
||||||
public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
public static final String CONFIG_STANDARD_USER =
|
||||||
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_ROLE + ":" + DEFAULT_USER_NAME+ "\n";
|
DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n" +
|
||||||
public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" +
|
DEFAULT_TRANSPORT_CLIENT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
||||||
" cluster: ALL\n" +
|
|
||||||
" indices:\n" +
|
public static final String CONFIG_STANDARD_USER_ROLES =
|
||||||
" '*': ALL\n";
|
DEFAULT_ROLE + ":" + DEFAULT_USER_NAME + "," + DEFAULT_TRANSPORT_CLIENT_USER_NAME + "\n" +
|
||||||
|
DEFAULT_TRANSPORT_CLIENT_ROLE + ":" + DEFAULT_TRANSPORT_CLIENT_USER_NAME+ "\n";
|
||||||
|
|
||||||
|
public static final String CONFIG_ROLE_ALLOW_ALL =
|
||||||
|
DEFAULT_ROLE + ":\n" +
|
||||||
|
" cluster: ALL\n" +
|
||||||
|
" indices:\n" +
|
||||||
|
" '*': ALL\n" +
|
||||||
|
"transport_client:\n" +
|
||||||
|
" cluster:\n" +
|
||||||
|
" - cluster:monitor/nodes/info\n" +
|
||||||
|
" - cluster:monitor/state";
|
||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static TemporaryFolder tmpFolder = new TemporaryFolder();
|
public static TemporaryFolder tmpFolder = new TemporaryFolder();
|
||||||
|
@ -103,9 +119,10 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
||||||
@Override
|
@Override
|
||||||
protected Settings transportClientSettings() {
|
protected Settings transportClientSettings() {
|
||||||
ImmutableSettings.Builder builder = ImmutableSettings.builder()
|
ImmutableSettings.Builder builder = ImmutableSettings.builder()
|
||||||
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
.put("shield.user", getClientUsername() + ":" + getClientPassword())
|
||||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
||||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||||
|
.put("plugin.types", ShieldPlugin.class.getName())
|
||||||
.put("node.mode", "network")
|
.put("node.mode", "network")
|
||||||
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient"));
|
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient"));
|
||||||
|
|
||||||
|
@ -144,7 +161,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getClientUsername() {
|
protected String getClientUsername() {
|
||||||
return DEFAULT_USER_NAME;
|
return DEFAULT_TRANSPORT_CLIENT_USER_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SecuredString getClientPassword() {
|
protected SecuredString getClientPassword() {
|
||||||
|
|
|
@ -67,12 +67,15 @@ public class TransportFilterTests extends ElasticsearchIntegrationTest {
|
||||||
targetService.sendRequest(sourceNode, "_action", new Request("trgt_to_src"), new ResponseHandler(new Response("src_to_trgt"), latch));
|
targetService.sendRequest(sourceNode, "_action", new Request("trgt_to_src"), new ResponseHandler(new Response("src_to_trgt"), latch));
|
||||||
await(latch);
|
await(latch);
|
||||||
|
|
||||||
|
ServerTransportFilter sourceServerFilter = internalCluster().getInstance(ServerTransportFilter.class, source);
|
||||||
ServerTransportFilter sourceFilter = internalCluster().getInstance(ServerTransportFilter.class, source);
|
ClientTransportFilter sourceClientFilter = internalCluster().getInstance(ClientTransportFilter.class, source);
|
||||||
ServerTransportFilter targetFilter = internalCluster().getInstance(ServerTransportFilter.class, target);
|
ServerTransportFilter targetServerFilter = internalCluster().getInstance(ServerTransportFilter.class, target);
|
||||||
InOrder inOrder = inOrder(sourceFilter, targetFilter);
|
ClientTransportFilter targetClientFilter = internalCluster().getInstance(ClientTransportFilter.class, target);
|
||||||
inOrder.verify(targetFilter).inbound("_action", new Request("src_to_trgt"));
|
InOrder inOrder = inOrder(sourceServerFilter, sourceClientFilter, targetServerFilter, targetClientFilter);
|
||||||
inOrder.verify(sourceFilter).inbound("_action", new Request("trgt_to_src"));
|
inOrder.verify(sourceClientFilter).outbound("_action", new Request("src_to_trgt"));
|
||||||
|
inOrder.verify(targetServerFilter).inbound("_action", new Request("src_to_trgt"));
|
||||||
|
inOrder.verify(targetClientFilter).outbound("_action", new Request("trgt_to_src"));
|
||||||
|
inOrder.verify(sourceServerFilter).inbound("_action", new Request("trgt_to_src"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InternalPlugin extends AbstractPlugin {
|
public static class InternalPlugin extends AbstractPlugin {
|
||||||
|
@ -97,6 +100,7 @@ public class TransportFilterTests extends ElasticsearchIntegrationTest {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(ServerTransportFilter.class).toInstance(mock(ServerTransportFilter.class));
|
bind(ServerTransportFilter.class).toInstance(mock(ServerTransportFilter.class));
|
||||||
|
bind(ClientTransportFilter.class).toInstance(mock(ClientTransportFilter.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,14 @@ import org.elasticsearch.node.internal.InternalNode;
|
||||||
import org.elasticsearch.plugins.PluginsService;
|
import org.elasticsearch.plugins.PluginsService;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
||||||
|
import org.elasticsearch.shield.transport.SecuredTransportService;
|
||||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
||||||
import org.elasticsearch.transport.Transport;
|
import org.elasticsearch.transport.Transport;
|
||||||
import org.elasticsearch.transport.TransportModule;
|
import org.elasticsearch.transport.TransportModule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ssl.*;
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -94,13 +96,17 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectNodeWorks() throws Exception {
|
public void testConnectNodeWorks() throws Exception {
|
||||||
|
File folder = newFolder();
|
||||||
Settings settings = settingsBuilder()
|
Settings settings = settingsBuilder()
|
||||||
.put("name", "programmatic_node")
|
.put("name", "programmatic_node")
|
||||||
.put("cluster.name", internalCluster().getClusterName())
|
.put("cluster.name", internalCluster().getClusterName())
|
||||||
|
|
||||||
|
.put("shield.authc.esusers.files.users", writeFile(folder, "users", configUsers()))
|
||||||
|
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", configUsersRoles()))
|
||||||
|
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", configRole()))
|
||||||
|
|
||||||
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
||||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
|
||||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
|
||||||
|
|
||||||
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
||||||
.build();
|
.build();
|
||||||
|
@ -114,19 +120,23 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectNodeClientWorks() throws Exception {
|
public void testConnectNodeClientWorks() throws Exception {
|
||||||
|
File folder = newFolder();
|
||||||
Settings settings = settingsBuilder()
|
Settings settings = settingsBuilder()
|
||||||
.put("name", "programmatic_node_client")
|
.put("name", "programmatic_node_client")
|
||||||
.put("cluster.name", internalCluster().getClusterName())
|
.put("cluster.name", internalCluster().getClusterName())
|
||||||
.put("node.mode", "network")
|
.put("node.mode", "network")
|
||||||
|
|
||||||
|
.put("shield.authc.esusers.files.users", writeFile(folder, "users", configUsers()))
|
||||||
|
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", configUsersRoles()))
|
||||||
|
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", configRole()))
|
||||||
|
|
||||||
.put("discovery.zen.ping.multicast.enabled", false)
|
.put("discovery.zen.ping.multicast.enabled", false)
|
||||||
.put("discovery.type", "zen")
|
.put("discovery.type", "zen")
|
||||||
.putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress())
|
.putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress())
|
||||||
|
|
||||||
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
||||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
|
||||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
.put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL))
|
||||||
.put("shield.transport.n2n.ip_filter.file", writeFile(newFolder(), "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL))
|
|
||||||
|
|
||||||
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||||
|
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,12 +42,13 @@ public class SslMultiPortTests extends ShieldIntegrationTest {
|
||||||
@Before
|
@Before
|
||||||
public void setupBuilder() {
|
public void setupBuilder() {
|
||||||
builder = settingsBuilder()
|
builder = settingsBuilder()
|
||||||
|
// we have to set the user with the Authorization header as shield.user only works when
|
||||||
|
// shield is installed on the client as a plugin
|
||||||
|
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
||||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
||||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||||
.put("node.mode", "network")
|
.put("node.mode", "network")
|
||||||
.put("cluster.name", internalCluster().getClusterName());
|
.put("cluster.name", internalCluster().getClusterName());
|
||||||
|
|
||||||
setUser(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue