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:
uboness 2014-10-21 18:24:57 +02:00
parent 2b108203fb
commit 6087480368
28 changed files with 670 additions and 340 deletions

View File

@ -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) {

View File

@ -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),

View File

@ -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 {
} }
} }
} }

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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)

View File

@ -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;
} }

View File

@ -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);
return token;
}
}
throw new AuthenticationException("Missing authentication token"); throw new AuthenticationException("Missing authentication token");
} }
User user = authenticate(request, token);
@Override if (user == null) {
public AuthenticationToken token(String action, TransportMessage<?> message) { throw new AuthenticationException("Unable to authenticate user for request");
return token(action, message, null); }
return user;
} }
@Override @Override
@SuppressWarnings("unchecked") public User authenticate(String action, TransportMessage message, User fallbackUser) {
public AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken) { User user = (User) message.getContext().get(USER_KEY);
AuthenticationToken token = message.getFromContext(TOKEN_CTX_KEY); if (user != null) {
if (token != null) { return user;
return token;
} }
for (Realm realm : realms) { String header = (String) message.getHeader(USER_KEY);
token = realm.token(message); if (header != null) {
if (token != null) { if (signUserHeader) {
header = keyService.unsignAndVerify(header);
if (logger.isTraceEnabled()) { }
logger.trace("Realm [{}] resolved auth token [{}] from transport request with action [{}]", realm.type(), token.principal(), action); 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); @Override
return token; 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) { static User decodeUser(String text) {
if (auditTrail != null) { byte[] bytes = Base64.decodeBase64(text);
auditTrail.anonymousAccess(action, message); 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); static String encodeUser(User user, ESLogger logger) {
return defaultToken; 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 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);
if (token == null) {
if (fallbackUser == null) {
auditTrail.anonymousAccess(action, message);
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
}
return fallbackUser;
}
try { 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) { if (user != null) {
return user; 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); 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;
}
} }

View File

@ -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;
} }
} }

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}

View File

@ -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();
} }

View File

@ -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 {

View File

@ -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")))

View File

@ -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);
} }

View File

@ -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()) {

View File

@ -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> {

View File

@ -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 {

View File

@ -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){

View File

@ -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]"));
} }
} }

View File

@ -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";
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" + " cluster: ALL\n" +
" indices:\n" + " indices:\n" +
" '*': ALL\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() {

View File

@ -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));
} }
} }

View File

@ -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();

View File

@ -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