diff --git a/src/main/java/org/elasticsearch/shield/SecurityFilter.java b/src/main/java/org/elasticsearch/shield/SecurityFilter.java index f82a59d4a97..fb6882f3f70 100644 --- a/src/main/java/org/elasticsearch/shield/SecurityFilter.java +++ b/src/main/java/org/elasticsearch/shield/SecurityFilter.java @@ -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 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) { diff --git a/src/main/java/org/elasticsearch/shield/ShieldModule.java b/src/main/java/org/elasticsearch/shield/ShieldModule.java index b6d26a4e7c1..4b5b58a5f92 100644 --- a/src/main/java/org/elasticsearch/shield/ShieldModule.java +++ b/src/main/java/org/elasticsearch/shield/ShieldModule.java @@ -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.of( new SecuredTransportModule(settings), new SSLModule(settings)); } - return ImmutableList.of( + return ImmutableList.of( new AuthenticationModule(settings), new AuthorizationModule(settings), new AuditTrailModule(settings), diff --git a/src/main/java/org/elasticsearch/shield/User.java b/src/main/java/org/elasticsearch/shield/User.java index 49329562747..72e32a15168 100644 --- a/src/main/java/org/elasticsearch/shield/User.java +++ b/src/main/java/org/elasticsearch/shield/User.java @@ -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 { } } - } diff --git a/src/main/java/org/elasticsearch/shield/audit/AuditTrail.java b/src/main/java/org/elasticsearch/shield/audit/AuditTrail.java index ab3c64681e6..faf92268ed0 100644 --- a/src/main/java/org/elasticsearch/shield/audit/AuditTrail.java +++ b/src/main/java/org/elasticsearch/shield/audit/AuditTrail.java @@ -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); diff --git a/src/main/java/org/elasticsearch/shield/audit/AuditTrailService.java b/src/main/java/org/elasticsearch/shield/audit/AuditTrailService.java index ccb46a140f9..56984162b3d 100644 --- a/src/main/java/org/elasticsearch/shield/audit/AuditTrailService.java +++ b/src/main/java/org/elasticsearch/shield/audit/AuditTrailService.java @@ -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) { diff --git a/src/main/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrail.java b/src/main/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrail.java index e9b3bf68c52..136d729b21f 100644 --- a/src/main/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrail.java +++ b/src/main/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrail.java @@ -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); diff --git a/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java b/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java index 2c4808ed1c3..03d0e10636c 100644 --- a/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java +++ b/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java @@ -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 spawnModules() { return ImmutableList.of( - new SystemRealm.Module(settings), new ESUsersModule(settings), new LdapModule(settings), new ActiveDirectoryModule(settings) diff --git a/src/main/java/org/elasticsearch/shield/authc/AuthenticationService.java b/src/main/java/org/elasticsearch/shield/authc/AuthenticationService.java index 10ed66badcc..0d01ecdd880 100644 --- a/src/main/java/org/elasticsearch/shield/authc/AuthenticationService.java +++ b/src/main/java/org/elasticsearch/shield/authc/AuthenticationService.java @@ -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; } diff --git a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java index 45b55b735ef..5cbaef2e177 100644 --- a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java +++ b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java @@ -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"); } - throw new AuthenticationException("Missing authentication token"); + User user = authenticate(request, token); + if (user == null) { + throw new AuthenticationException("Unable to authenticate user for request"); + } + return user; } @Override - public AuthenticationToken token(String action, TransportMessage message) { - return token(action, message, null); + public User authenticate(String action, TransportMessage message, User fallbackUser) { + User user = (User) message.getContext().get(USER_KEY); + if (user != null) { + return user; + } + String header = (String) message.getHeader(USER_KEY); + if (header != null) { + if (signUserHeader) { + header = keyService.unsignAndVerify(header); + } + user = decodeUser(header); + } + if (user == null) { + user = authenticateWithRealms(action, message, fallbackUser); + header = signUserHeader ? keyService.sign(encodeUser(user, logger)) : encodeUser(user, logger); + message.putHeader(USER_KEY, header); + } + message.putInContext(USER_KEY, user); + return user; } @Override - @SuppressWarnings("unchecked") - public AuthenticationToken token(String action, TransportMessage message, AuthenticationToken defaultToken) { - AuthenticationToken token = message.getFromContext(TOKEN_CTX_KEY); - if (token != null) { - 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); } - for (Realm realm : realms) { - token = realm.token(message); - if (token != null) { + if (header == null) { + message.putInContext(USER_KEY, user); + header = signUserHeader ? keyService.sign(encodeUser(user, logger)) : encodeUser(user, logger); + message.putHeader(USER_KEY, header); + } + } - if (logger.isTraceEnabled()) { - logger.trace("Realm [{}] resolved auth token [{}] from transport request with action [{}]", realm.type(), token.principal(), action); - } + 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); + } + } - message.putInContext(TOKEN_CTX_KEY, token); - return token; + 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; } - - if (defaultToken == null) { - if (auditTrail != null) { - auditTrail.anonymousAccess(action, message); - } - throw new AuthenticationException("Missing authentication token for request [" + action + "]"); - } - - message.putInContext(TOKEN_CTX_KEY, defaultToken); - return defaultToken; } /** @@ -92,63 +126,91 @@ public class InternalAuthenticationService extends AbstractComponent implements * * The order by which the realms are checked is defined in {@link Realms}. * - * @param action The executed action - * @param message The executed request - * @param token The authentication token - * @return The authenticated user + * @param action The executed action + * @param message The executed request + * @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"; - try { - User user = (User) message.getContext().get(USER_CTX_KEY); - if (user != null) { - return user; + 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 { for (Realm realm : realms) { if (realm.supports(token)) { - user = realm.authenticate(token); + User 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"); } 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); } + auditTrail.authenticationFailed(realm.type(), token, request); } } - if (auditTrail != null) { - auditTrail.authenticationFailed(token, request); - } - throw new AuthenticationException("Unable to authenticate user for request"); + auditTrail.authenticationFailed(token, 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; + } } diff --git a/src/main/java/org/elasticsearch/shield/authc/Realms.java b/src/main/java/org/elasticsearch/shield/authc/Realms.java index 1507820cfe0..49138c3c3f1 100644 --- a/src/main/java/org/elasticsearch/shield/authc/Realms.java +++ b/src/main/java/org/elasticsearch/shield/authc/Realms.java @@ -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 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; } - } diff --git a/src/main/java/org/elasticsearch/shield/authc/system/SystemRealm.java b/src/main/java/org/elasticsearch/shield/authc/system/SystemRealm.java deleted file mode 100644 index 847bf48d7c4..00000000000 --- a/src/main/java/org/elasticsearch/shield/authc/system/SystemRealm.java +++ /dev/null @@ -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 { - - 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(); - } - } -} diff --git a/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java b/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java index a2e936c03a6..042a8c6a981 100644 --- a/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java +++ b/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java @@ -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; diff --git a/src/main/java/org/elasticsearch/shield/key/tool/SystemKeyTool.java b/src/main/java/org/elasticsearch/shield/key/tool/SystemKeyTool.java index 2e994613793..e7847cf491d 100644 --- a/src/main/java/org/elasticsearch/shield/key/tool/SystemKeyTool.java +++ b/src/main/java/org/elasticsearch/shield/key/tool/SystemKeyTool.java @@ -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; diff --git a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java index 0a347847948..1d572ebb77c 100644 --- a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java +++ b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java @@ -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) { diff --git a/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java b/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java new file mode 100644 index 00000000000..d54123a14a4 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java @@ -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); + +} diff --git a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java index 017d0ea8993..be9c5f7fe0a 100644 --- a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java +++ b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java @@ -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(); } diff --git a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportService.java b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportService.java index 72c70b2ff11..74f3cd7584a 100644 --- a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportService.java +++ b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportService.java @@ -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 void sendRequest(DiscoveryNode node, String action, TransportRequest request, TransportRequestOptions options, TransportResponseHandler 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 { diff --git a/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java b/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java index fa8b46bfdc5..e41ea274edf 100644 --- a/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java +++ b/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java @@ -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"))) diff --git a/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java b/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java index 2be3481174a..4f280a1e59c 100644 --- a/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java +++ b/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java @@ -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); } diff --git a/src/test/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrailTests.java b/src/test/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrailTests.java index ed26b08bd5b..3ca455a4a97 100644 --- a/src/test/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrailTests.java +++ b/src/test/java/org/elasticsearch/shield/audit/logfile/LoggingAuditTrailTests.java @@ -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()) { diff --git a/src/test/java/org/elasticsearch/shield/authc/InternalAuthenticationServiceTests.java b/src/test/java/org/elasticsearch/shield/authc/InternalAuthenticationServiceTests.java index bbb48953f8d..5cb2229bcaa 100644 --- a/src/test/java/org/elasticsearch/shield/authc/InternalAuthenticationServiceTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/InternalAuthenticationServiceTests.java @@ -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 { diff --git a/src/test/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealmTests.java b/src/test/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealmTests.java index dca3d3052d7..d3908c4bce7 100644 --- a/src/test/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealmTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealmTests.java @@ -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 { diff --git a/src/test/java/org/elasticsearch/shield/authc/support/SecuredStringTests.java b/src/test/java/org/elasticsearch/shield/authc/support/SecuredStringTests.java index 7229dab1690..6989b869d6c 100644 --- a/src/test/java/org/elasticsearch/shield/authc/support/SecuredStringTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/support/SecuredStringTests.java @@ -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){ diff --git a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java index 55e005e9ea7..d1e0287f30e 100644 --- a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java @@ -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]")); } } diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index adf1256a120..5c3d73f8d3f 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -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" + - " cluster: ALL\n" + - " indices:\n" + - " '*': ALL\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" + + "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() { diff --git a/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java b/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java index aa77c52544f..004c8ac7725 100644 --- a/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java @@ -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)); } } diff --git a/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java b/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java index 2016ff3e766..81e83df6eee 100644 --- a/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java @@ -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(); diff --git a/src/test/java/org/elasticsearch/shield/transport/ssl/SslMultiPortTests.java b/src/test/java/org/elasticsearch/shield/transport/ssl/SslMultiPortTests.java index 3fe16d0fac7..a1ded90407e 100644 --- a/src/test/java/org/elasticsearch/shield/transport/ssl/SslMultiPortTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslMultiPortTests.java @@ -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