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.shield.audit.AuditTrail;
|
||||
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.AuthorizationService;
|
||||
import org.elasticsearch.shield.authz.SystemRole;
|
||||
import org.elasticsearch.shield.key.KeyService;
|
||||
import org.elasticsearch.shield.key.SignatureException;
|
||||
import org.elasticsearch.shield.transport.ClientTransportFilter;
|
||||
import org.elasticsearch.shield.transport.ServerTransportFilter;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
|
@ -51,22 +49,14 @@ public class SecurityFilter extends AbstractComponent {
|
|||
this.auditTrail = auditTrail;
|
||||
}
|
||||
|
||||
User authenticateAndAuthorize(String action, TransportRequest request) {
|
||||
|
||||
// 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);
|
||||
|
||||
User authenticateAndAuthorize(String action, TransportRequest request, User fallbackUser) {
|
||||
User user = authcService.authenticate(action, request, fallbackUser);
|
||||
authzService.authorize(user, action, request);
|
||||
return user;
|
||||
}
|
||||
|
||||
User authenticate(RestRequest request) {
|
||||
AuthenticationToken token = authcService.token(request);
|
||||
return authcService.authenticate(request, token);
|
||||
return authcService.authenticate(request);
|
||||
}
|
||||
|
||||
<Request extends ActionRequest> Request unsign(User user, String action, Request request) {
|
||||
|
@ -151,7 +141,26 @@ public class SecurityFilter extends AbstractComponent {
|
|||
|
||||
@Override
|
||||
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
|
||||
public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) {
|
||||
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);
|
||||
chain.proceed(action, request, new SigningListener(user, action, filter, listener));
|
||||
} catch (Throwable t) {
|
||||
|
|
|
@ -47,12 +47,12 @@ public class ShieldModule extends AbstractShieldModule.Spawn implements PreProce
|
|||
|
||||
// spawn needed parts in client mode
|
||||
if (clientMode) {
|
||||
return ImmutableList.of(
|
||||
return ImmutableList.<Module>of(
|
||||
new SecuredTransportModule(settings),
|
||||
new SSLModule(settings));
|
||||
}
|
||||
|
||||
return ImmutableList.of(
|
||||
return ImmutableList.<Module>of(
|
||||
new AuthenticationModule(settings),
|
||||
new AuthorizationModule(settings),
|
||||
new AuditTrailModule(settings),
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
*/
|
||||
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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +35,29 @@ public abstract class User {
|
|||
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 {
|
||||
|
||||
private final String username;
|
||||
|
@ -38,7 +65,7 @@ public abstract class User {
|
|||
|
||||
public Simple(String username, String... roles) {
|
||||
this.username = username;
|
||||
this.roles = roles;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,5 +118,4 @@ public abstract class User {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ public interface AuditTrail {
|
|||
public void anonymousAccess(String action, TransportMessage<?> message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void anonymousAccess(RestRequest request) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
||||
}
|
||||
|
@ -62,6 +66,8 @@ public interface AuditTrail {
|
|||
|
||||
void anonymousAccess(String action, TransportMessage<?> message);
|
||||
|
||||
void anonymousAccess(RestRequest request);
|
||||
|
||||
void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message);
|
||||
|
||||
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
|
||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
|
||||
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
|
||||
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> 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.esusers.ESUsersModule;
|
||||
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||
|
||||
/**
|
||||
|
@ -25,7 +24,6 @@ public class AuthenticationModule extends AbstractShieldModule.Node.Spawn {
|
|||
@Override
|
||||
public Iterable<? extends Node> spawnModules() {
|
||||
return ImmutableList.of(
|
||||
new SystemRealm.Module(settings),
|
||||
new ESUsersModule(settings),
|
||||
new LdapModule(settings),
|
||||
new ActiveDirectoryModule(settings)
|
||||
|
|
|
@ -15,52 +15,45 @@ import org.elasticsearch.transport.TransportMessage;
|
|||
public interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* Extracts an authentication token from the given rest request and if found registers it on
|
||||
* the request. If not found, an {@link AuthenticationException} is thrown.
|
||||
* Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e.
|
||||
* 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
|
||||
* with the message, an AuthenticationException is thrown.
|
||||
* Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e.
|
||||
* 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
|
||||
* with the message and the given defaultToken is not {@code null}, the default token will be returned.
|
||||
* Otherwise an AuthenticationException is thrown.
|
||||
* Checks if there's alreay a user header attached to the given message. If missing, a new header is
|
||||
* set on the message with the given user (encoded).
|
||||
*
|
||||
* @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;
|
||||
|
||||
import org.elasticsearch.common.ContextHolder;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
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.rest.RestRequest;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.audit.AuditTrail;
|
||||
import org.elasticsearch.shield.key.KeyService;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -22,65 +27,94 @@ import org.elasticsearch.transport.TransportMessage;
|
|||
*/
|
||||
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {
|
||||
|
||||
static final String TOKEN_CTX_KEY = "_shield_token";
|
||||
static final String USER_CTX_KEY = "_shield_user";
|
||||
static final String TOKEN_KEY = "_shield_token";
|
||||
static final String USER_KEY = "_shield_user";
|
||||
|
||||
private final Realm[] realms;
|
||||
private final AuditTrail auditTrail;
|
||||
private final KeyService keyService;
|
||||
private final boolean signUserHeader;
|
||||
|
||||
@Inject
|
||||
public InternalAuthenticationService(Settings settings, Realms realms, @Nullable AuditTrail auditTrail) {
|
||||
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, KeyService keyService) {
|
||||
super(settings);
|
||||
this.realms = realms.realms();
|
||||
this.auditTrail = auditTrail;
|
||||
this.keyService = keyService;
|
||||
this.signUserHeader = componentSettings.getAsBoolean("sign_user_header", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationToken token(RestRequest request) throws AuthenticationException {
|
||||
for (Realm realm : realms) {
|
||||
AuthenticationToken token = realm.token(request);
|
||||
if (token != null) {
|
||||
request.putInContext(TOKEN_CTX_KEY, token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
public User authenticate(RestRequest request) throws AuthenticationException {
|
||||
AuthenticationToken token = token(request);
|
||||
if (token == null) {
|
||||
auditTrail.anonymousAccess(request);
|
||||
throw new AuthenticationException("Missing authentication token");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationToken token(String action, TransportMessage<?> message) {
|
||||
return token(action, message, null);
|
||||
User user = authenticate(request, token);
|
||||
if (user == null) {
|
||||
throw new AuthenticationException("Unable to authenticate user for request");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken) {
|
||||
AuthenticationToken token = message.getFromContext(TOKEN_CTX_KEY);
|
||||
if (token != null) {
|
||||
return token;
|
||||
public User authenticate(String action, TransportMessage message, User fallbackUser) {
|
||||
User user = (User) message.getContext().get(USER_KEY);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
message.putInContext(TOKEN_CTX_KEY, token);
|
||||
return token;
|
||||
@Override
|
||||
public void attachUserHeaderIfMissing(TransportMessage message, User user) {
|
||||
String header = (String) message.getHeader(USER_KEY);
|
||||
if (header == null) {
|
||||
header = (String) message.getHeader(TOKEN_KEY);
|
||||
}
|
||||
if (header == null) {
|
||||
message.putInContext(USER_KEY, user);
|
||||
header = signUserHeader ? keyService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
||||
message.putHeader(USER_KEY, header);
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultToken == null) {
|
||||
if (auditTrail != null) {
|
||||
auditTrail.anonymousAccess(action, message);
|
||||
static User decodeUser(String text) {
|
||||
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);
|
||||
}
|
||||
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
||||
}
|
||||
|
||||
message.putInContext(TOKEN_CTX_KEY, defaultToken);
|
||||
return defaultToken;
|
||||
static String encodeUser(User user, ESLogger logger) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,61 +128,89 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
*
|
||||
* @param action The executed action
|
||||
* @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
|
||||
* @throws AuthenticationException If none of the configured realms successfully authenticated the
|
||||
* request
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException {
|
||||
assert token != null : "cannot authenticate null tokens";
|
||||
User authenticateWithRealms(String action, TransportMessage<?> message, User fallbackUser) throws AuthenticationException {
|
||||
AuthenticationToken token = token(action, message);
|
||||
|
||||
if (token == null) {
|
||||
if (fallbackUser == null) {
|
||||
auditTrail.anonymousAccess(action, message);
|
||||
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
||||
}
|
||||
return fallbackUser;
|
||||
}
|
||||
|
||||
try {
|
||||
User user = (User) message.getContext().get(USER_CTX_KEY);
|
||||
for (Realm realm : realms) {
|
||||
if (realm.supports(token)) {
|
||||
User user = realm.authenticate(token);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
for (Realm realm : realms) {
|
||||
if (realm.supports(token)) {
|
||||
user = realm.authenticate(token);
|
||||
if (user != null) {
|
||||
message.putInContext(USER_CTX_KEY, user);
|
||||
return user;
|
||||
} else if (auditTrail != null) {
|
||||
auditTrail.authenticationFailed(realm.type(), token, action, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auditTrail != null) {
|
||||
auditTrail.authenticationFailed(token, action, message);
|
||||
}
|
||||
throw new AuthenticationException("Unable to authenticate user for request");
|
||||
} finally {
|
||||
token.clearCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException {
|
||||
User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException {
|
||||
assert token != null : "cannot authenticate null tokens";
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
if (realm.supports(token)) {
|
||||
User user = realm.authenticate(token);
|
||||
if (user != null) {
|
||||
request.putInContext(USER_CTX_KEY, user);
|
||||
request.putInContext(USER_KEY, user);
|
||||
return user;
|
||||
} else if (auditTrail != null) {
|
||||
}
|
||||
auditTrail.authenticationFailed(realm.type(), token, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auditTrail != null) {
|
||||
auditTrail.authenticationFailed(token, request);
|
||||
}
|
||||
throw new AuthenticationException("Unable to authenticate user for request");
|
||||
return null;
|
||||
} finally {
|
||||
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.esusers.ESUsersRealm;
|
||||
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -27,13 +26,9 @@ public class Realms {
|
|||
private final Realm[] realms;
|
||||
|
||||
@Inject
|
||||
public Realms(SystemRealm system,
|
||||
@Nullable ESUsersRealm esusers,
|
||||
@Nullable LdapRealm ldap,
|
||||
@Nullable ActiveDirectoryRealm activeDirectory) {
|
||||
public Realms(@Nullable ESUsersRealm esusers, @Nullable LdapRealm ldap, @Nullable ActiveDirectoryRealm activeDirectory) {
|
||||
|
||||
List<Realm> realms = new ArrayList<>();
|
||||
realms.add(system);
|
||||
if (esusers != null) {
|
||||
logger.info("Realm [" + esusers.type() + "] is used");
|
||||
realms.add(esusers);
|
||||
|
@ -52,5 +47,4 @@ public class Realms {
|
|||
Realm[] 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 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;
|
||||
|
||||
|
|
|
@ -45,8 +45,8 @@ public class SystemKeyTool extends CliTool {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -24,16 +24,16 @@ import java.util.Arrays;
|
|||
*/
|
||||
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_HTTP_SSL = "shield.http.ssl";
|
||||
public static final String SHIELD_AUTHC_LDAP_URL = "shield.authc.ldap.url";
|
||||
|
||||
private final TrustManagerFactory trustFactory;
|
||||
private final SSLContext sslContext;
|
||||
private final String[] ciphers;
|
||||
private final KeyManagerFactory keyManagerFactory;
|
||||
private final String sslProtocol;
|
||||
private final SSLContext sslContext;
|
||||
private final String[] ciphers;
|
||||
|
||||
@Inject
|
||||
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) {
|
||||
// no ip filtering on the client
|
||||
bind(ServerTransportFilter.class).toInstance(ServerTransportFilter.NOOP);
|
||||
bind(ClientTransportFilter.class).toInstance(ClientTransportFilter.NOOP);
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
bind(IPFilteringN2NAuthenticator.class).asEagerSingleton();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.shield.transport;
|
||||
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
@ -15,17 +16,29 @@ import org.elasticsearch.transport.*;
|
|||
*/
|
||||
public class SecuredTransportService extends TransportService {
|
||||
|
||||
private final ServerTransportFilter filter;
|
||||
private final ServerTransportFilter serverFilter;
|
||||
private final ClientTransportFilter clientFilter;
|
||||
|
||||
@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);
|
||||
this.filter = filter;
|
||||
this.serverFilter = serverFilter;
|
||||
this.clientFilter = clientFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
|
|
|
@ -12,9 +12,11 @@ import org.elasticsearch.cluster.ClusterService;
|
|||
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
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.SecuredStringTests;
|
||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -39,16 +41,23 @@ public class PermissionPrecedenceTests extends ShieldIntegrationTest {
|
|||
" indices:\n" +
|
||||
" '*': all\n" +
|
||||
"\n" +
|
||||
"transport_client:\n" +
|
||||
" cluster:\n" +
|
||||
" - cluster:monitor/nodes/info\n" +
|
||||
" - cluster:monitor/state\n" +
|
||||
"\n" +
|
||||
"user:\n" +
|
||||
" indices:\n" +
|
||||
" 'test_*': all\n";
|
||||
|
||||
static final String USERS =
|
||||
"admin:{plain}test123\n" +
|
||||
"client:{plain}test123\n" +
|
||||
"user:{plain}test123\n";
|
||||
|
||||
static final String USERS_ROLES =
|
||||
"admin:admin\n" +
|
||||
"transport_client:client\n" +
|
||||
"user:user\n";
|
||||
|
||||
@Override
|
||||
|
@ -83,6 +92,7 @@ public class PermissionPrecedenceTests extends ShieldIntegrationTest {
|
|||
TransportAddress address = clusterService.localNode().address();
|
||||
|
||||
try (TransportClient client = new TransportClient(ImmutableSettings.builder()
|
||||
.put("shield.user", "client:test123")
|
||||
.put("cluster.name", internalCluster().getClusterName())
|
||||
.put("node.mode", "network")
|
||||
.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.authc.AuthenticationException;
|
||||
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.AuthorizationService;
|
||||
import org.elasticsearch.shield.key.KeyService;
|
||||
|
@ -59,36 +57,30 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testProcess() throws Exception {
|
||||
public void testAuthenticateAndAuthorize() throws Exception {
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
filter.authenticateAndAuthorize("_action", request);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||
filter.authenticateAndAuthorize("_action", request, null);
|
||||
verify(authzService).authorize(user, "_action", request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_InternalAction() throws Exception {
|
||||
public void testAuthenticateAndAuthorize_InternalAction() throws Exception {
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("internal:_action", request, SystemRealm.TOKEN)).thenReturn(token);
|
||||
when(authcService.authenticate("internal:_action", request, token)).thenReturn(user);
|
||||
filter.authenticateAndAuthorize("internal:_action", request);
|
||||
when(authcService.authenticate("internal:_action", request, User.SYSTEM)).thenReturn(user);
|
||||
filter.authenticateAndAuthorize("internal:_action", request, User.SYSTEM);
|
||||
verify(authzService).authorize(user, "internal:_action", request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthenticationFails_Authenticate() throws Exception {
|
||||
public void testAuthenticateAndAuthorize_AuthenticationFails_Authenticate() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.authenticateAndAuthorize("_action", request);
|
||||
when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.authenticateAndAuthorize("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -96,42 +88,38 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(authcService.token(request)).thenReturn(token);
|
||||
when(authcService.authenticate(request, token)).thenThrow(new AuthenticationException("failed authc"));
|
||||
when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.authenticate(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthenticationFails_NoToken() throws Exception {
|
||||
public void testAuthenticateAndAuthorize_AuthenticationFails_NoToken() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
TransportRequest request = new InternalRequest();
|
||||
when(authcService.token("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.authenticateAndAuthorize("_action", request);
|
||||
when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.authenticateAndAuthorize("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_Rest_AuthenticationFails_NoToken() throws Exception {
|
||||
public void testAuthenticateAndAuthorize_Rest_AuthenticationFails_NoToken() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthorizationFails() throws Exception {
|
||||
public void testAuthenticateAndAuthorize_AuthorizationFails() throws Exception {
|
||||
thrown.expect(AuthorizationException.class);
|
||||
thrown.expectMessage("failed authz");
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
when(authcService.authenticate("_action", request, null)).thenReturn(user);
|
||||
doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request);
|
||||
filter.authenticateAndAuthorize("_action", request);
|
||||
filter.authenticateAndAuthorize("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -140,7 +128,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
||||
InternalRequest request = new InternalRequest();
|
||||
transport.inbound("_action", request);
|
||||
verify(filter).authenticateAndAuthorize("_action", request);
|
||||
verify(filter).authenticateAndAuthorize("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -150,7 +138,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
filter = mock(SecurityFilter.class);
|
||||
SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -163,7 +151,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
when(filter.unsign(any(User.class), eq("_action"), eq(request))).thenReturn(request);
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -175,7 +163,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
ActionListener listener = mock(ActionListener.class);
|
||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
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);
|
||||
verify(listener).onFailure(exception);
|
||||
verifyNoMoreInteractions(chain);
|
||||
|
@ -189,9 +177,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
SignatureException sigException = new SignatureException("bad bad boy");
|
||||
User user = mock(User.class);
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||
when(keyService.signed("scroll_id")).thenReturn(true);
|
||||
doThrow(sigException).when(keyService).unsignAndVerify("scroll_id");
|
||||
action.apply("_action", request, listener, chain);
|
||||
|
@ -207,7 +193,6 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
RestChannel channel = mock(RestChannel.class);
|
||||
RestFilterChain chain = mock(RestFilterChain.class);
|
||||
rest.process(request, channel, chain);
|
||||
verify(authcService).token(request);
|
||||
verify(restController).registerFilter(rest);
|
||||
}
|
||||
|
||||
|
@ -220,7 +205,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
|
|||
RestRequest request = mock(RestRequest.class);
|
||||
RestChannel channel = mock(RestChannel.class);
|
||||
RestFilterChain chain = mock(RestFilterChain.class);
|
||||
doThrow(exception).when(authcService).token(request);
|
||||
doThrow(exception).when(authcService).authenticate(request);
|
||||
rest.process(request, channel, chain);
|
||||
verify(restController).registerFilter(rest);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import static org.mockito.Mockito.when;
|
|||
public class LoggingAuditTrailTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testAnonymousAccess() throws Exception {
|
||||
public void testAnonymousAccess_Transport() throws Exception {
|
||||
for (Level level : Level.values()) {
|
||||
CapturingLogger logger = new CapturingLogger(level);
|
||||
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
|
||||
public void testAuthenticationFailed() throws Exception {
|
||||
for (Level level : Level.values()) {
|
||||
|
|
|
@ -7,10 +7,14 @@ package org.elasticsearch.shield.authc;
|
|||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.Settings;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.audit.AuditTrail;
|
||||
import org.elasticsearch.shield.key.KeyService;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.junit.Before;
|
||||
|
@ -20,7 +24,6 @@ import org.junit.rules.ExpectedException;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
|
@ -36,10 +39,12 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
InternalAuthenticationService service;
|
||||
TransportMessage message;
|
||||
RestRequest restRequest;
|
||||
Realms realms;
|
||||
Realm firstRealm;
|
||||
Realm secondRealm;
|
||||
AuditTrail auditTrail;
|
||||
AuthenticationToken token;
|
||||
KeyService keyService;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
|
@ -50,11 +55,12 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
when(firstRealm.type()).thenReturn("first");
|
||||
secondRealm = mock(Realm.class);
|
||||
when(secondRealm.type()).thenReturn("second");
|
||||
Realms realms = mock(Realms.class);
|
||||
realms = mock(Realms.class);
|
||||
when(realms.realms()).thenReturn(new Realm[] {firstRealm, secondRealm});
|
||||
keyService = mock(KeyService.class);
|
||||
|
||||
auditTrail = mock(AuditTrail.class);
|
||||
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail);
|
||||
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, keyService);
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
|
@ -70,52 +76,23 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
|
||||
@Test
|
||||
public void testToken_Missing() throws Exception {
|
||||
try {
|
||||
service.token("_action", message);
|
||||
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);
|
||||
AuthenticationToken token = service.token("_action", message);
|
||||
assertThat(token, nullValue());
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_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));
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), nullValue());
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testToken_Cached() throws Exception {
|
||||
message.putInContext(InternalAuthenticationService.TOKEN_CTX_KEY, token);
|
||||
AuthenticationToken result = service.token("_action", message, token);
|
||||
message.putInContext(InternalAuthenticationService.TOKEN_KEY, token);
|
||||
AuthenticationToken result = service.token("_action", message);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verifyZeroInteractions(firstRealm);
|
||||
verifyZeroInteractions(secondRealm);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), notNullValue());
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_CTX_KEY), is((Object) token));
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), notNullValue());
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.TOKEN_KEY), is((Object) token));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
|
@ -126,12 +103,18 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
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, is(user));
|
||||
verify(auditTrail).authenticationFailed("first", token, "_action", message);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
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), sameInstance((Object) user));
|
||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
|
@ -141,27 +124,34 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
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, is(user));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verify(firstRealm, never()).authenticate(token);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
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));
|
||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_encoded_user"));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthenticate_Cached() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
message.putInContext(InternalAuthenticationService.USER_CTX_KEY, user);
|
||||
User result = service.authenticate("_action", message, token);
|
||||
message.putInContext(InternalAuthenticationService.USER_KEY, user);
|
||||
User result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verifyZeroInteractions(firstRealm);
|
||||
verifyZeroInteractions(secondRealm);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
||||
verifyZeroInteractions(keyService);
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -171,16 +161,222 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||
AuthenticationToken foundToken = service.token(restRequest);
|
||||
assertThat(foundToken, is(token));
|
||||
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_CTX_KEY), equalTo((Object) token));
|
||||
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_KEY), equalTo((Object) token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToken_Rest_Missing() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("Missing authentication token");
|
||||
when(firstRealm.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> {
|
||||
|
|
|
@ -14,7 +14,8 @@ import org.junit.Test;
|
|||
|
||||
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 {
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@ package org.elasticsearch.shield.authc.support;
|
|||
|
||||
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
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 static SecuredString build(String password){
|
||||
|
|
|
@ -195,7 +195,7 @@ public class IndicesResolverIntegrationTests extends ShieldIntegrationTest {
|
|||
actionRequestBuilder.get();
|
||||
fail("search should fail due to attempt to access non authorized indices");
|
||||
} 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.plugins.PluginsService;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
|
@ -34,6 +35,7 @@ import java.io.IOException;
|
|||
import java.nio.file.Path;
|
||||
|
||||
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.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
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_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_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
||||
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_ROLE + ":" + DEFAULT_USER_NAME+ "\n";
|
||||
public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" +
|
||||
public static final String CONFIG_STANDARD_USER =
|
||||
DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n" +
|
||||
DEFAULT_TRANSPORT_CLIENT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
||||
|
||||
public static final String CONFIG_STANDARD_USER_ROLES =
|
||||
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";
|
||||
" '*': ALL\n" +
|
||||
"transport_client:\n" +
|
||||
" cluster:\n" +
|
||||
" - cluster:monitor/nodes/info\n" +
|
||||
" - cluster:monitor/state";
|
||||
|
||||
@ClassRule
|
||||
public static TemporaryFolder tmpFolder = new TemporaryFolder();
|
||||
|
@ -103,9 +119,10 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
|||
@Override
|
||||
protected Settings transportClientSettings() {
|
||||
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("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.put("plugin.types", ShieldPlugin.class.getName())
|
||||
.put("node.mode", "network")
|
||||
.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() {
|
||||
return DEFAULT_USER_NAME;
|
||||
return DEFAULT_TRANSPORT_CLIENT_USER_NAME;
|
||||
}
|
||||
|
||||
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));
|
||||
await(latch);
|
||||
|
||||
|
||||
ServerTransportFilter sourceFilter = internalCluster().getInstance(ServerTransportFilter.class, source);
|
||||
ServerTransportFilter targetFilter = internalCluster().getInstance(ServerTransportFilter.class, target);
|
||||
InOrder inOrder = inOrder(sourceFilter, targetFilter);
|
||||
inOrder.verify(targetFilter).inbound("_action", new Request("src_to_trgt"));
|
||||
inOrder.verify(sourceFilter).inbound("_action", new Request("trgt_to_src"));
|
||||
ServerTransportFilter sourceServerFilter = internalCluster().getInstance(ServerTransportFilter.class, source);
|
||||
ClientTransportFilter sourceClientFilter = internalCluster().getInstance(ClientTransportFilter.class, source);
|
||||
ServerTransportFilter targetServerFilter = internalCluster().getInstance(ServerTransportFilter.class, target);
|
||||
ClientTransportFilter targetClientFilter = internalCluster().getInstance(ClientTransportFilter.class, target);
|
||||
InOrder inOrder = inOrder(sourceServerFilter, sourceClientFilter, targetServerFilter, targetClientFilter);
|
||||
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 {
|
||||
|
@ -97,6 +100,7 @@ public class TransportFilterTests extends ElasticsearchIntegrationTest {
|
|||
@Override
|
||||
protected void configure() {
|
||||
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.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
||||
import org.elasticsearch.shield.transport.SecuredTransportService;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.elasticsearch.transport.TransportModule;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
@ -94,13 +96,17 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testConnectNodeWorks() throws Exception {
|
||||
File folder = newFolder();
|
||||
Settings settings = settingsBuilder()
|
||||
.put("name", "programmatic_node")
|
||||
.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(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
|
||||
|
||||
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
||||
.build();
|
||||
|
@ -114,19 +120,23 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testConnectNodeClientWorks() throws Exception {
|
||||
File folder = newFolder();
|
||||
Settings settings = settingsBuilder()
|
||||
.put("name", "programmatic_node_client")
|
||||
.put("cluster.name", internalCluster().getClusterName())
|
||||
.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.type", "zen")
|
||||
.putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress())
|
||||
|
||||
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
|
||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.put("shield.transport.n2n.ip_filter.file", writeFile(newFolder(), "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL))
|
||||
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
|
||||
.put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL))
|
||||
|
||||
.put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient"))
|
||||
.build();
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
|||
import java.io.File;
|
||||
|
||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
|
@ -41,12 +42,13 @@ public class SslMultiPortTests extends ShieldIntegrationTest {
|
|||
@Before
|
||||
public void setupBuilder() {
|
||||
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("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.put("node.mode", "network")
|
||||
.put("cluster.name", internalCluster().getClusterName());
|
||||
|
||||
setUser(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue