From 22eea8aba0bef1db05fe928bcae2d849f875b47a Mon Sep 17 00:00:00 2001 From: uboness Date: Sat, 22 Nov 2014 05:31:03 +0100 Subject: [PATCH] [Cleanup] - Refactored security filter Nuked the security filter and separated the different filter to their own constructs: - Added a shield action package & module that is responsible for binding the shield action filter (and later will hold all shield actions) - Added a shield rest package & module that is responsible for binding the shield rest filter and registering all the rest actions - Moved the client & server transport filters to the transport package General cleanup: - Code formatting - moved `ShieldPlugin` to the top level package `org.elasticsearch.shield` Original commit: elastic/x-pack-elasticsearch@d652041860180d87872681f20cd95f407d35938b --- .../elasticsearch/shield/SecurityFilter.java | 233 ------------------ .../shield/SecurityFilterModule.java | 24 -- .../elasticsearch/shield/ShieldException.java | 1 - .../elasticsearch/shield/ShieldModule.java | 18 +- .../shield/{plugin => }/ShieldPlugin.java | 9 +- .../shield/action/ShieldActionFilter.java | 143 +++++++++++ .../shield/action/ShieldActionModule.java | 33 +++ .../shield/authc/AuthenticationModule.java | 3 +- .../authc/InternalAuthenticationService.java | 14 +- .../org/elasticsearch/shield/authc/Realm.java | 1 - .../ActiveDirectoryConnection.java | 26 +- .../ActiveDirectoryConnectionFactory.java | 14 +- .../ActiveDirectoryGroupToRoleMapper.java | 1 + .../authc/esusers/FileUserPasswdStore.java | 2 +- .../authc/esusers/FileUserRolesStore.java | 2 +- .../shield/authc/ldap/LdapConnection.java | 40 +-- .../authc/ldap/LdapConnectionFactory.java | 6 +- .../authc/ldap/LdapGroupToRoleMapper.java | 1 + .../shield/authc/ldap/LdapRealm.java | 5 +- .../shield/authc/support/CharArrays.java | 4 +- .../shield/authc/support/SecuredString.java | 33 +-- .../authc/support/UsernamePasswordToken.java | 1 - .../ldap/AbstractGroupToRoleMapper.java | 33 +-- .../authc/support/ldap/AbstractLdapRealm.java | 12 +- .../authc/support/ldap/ConnectionFactory.java | 10 +- .../support/ldap/LdapSslSocketFactory.java | 12 +- .../shield/authc/support/ldap/LdapUtils.java | 2 +- .../shield/authz/SystemRole.java | 1 + .../DefaultIndicesResolver.java | 6 +- .../shield/authz/store/FileRolesStore.java | 4 +- .../shield/key/InternalKeyService.java | 2 +- .../shield/rest/ShieldRestFilter.java | 40 +++ .../shield/rest/ShieldRestModule.java | 35 +++ .../action}/RestShieldInfoAction.java | 2 +- .../shield/support/AbstractShieldModule.java | 2 + .../transport/ClientTransportFilter.java | 42 +++- .../shield/transport/SecuredRestModule.java | 25 -- .../transport/SecuredTransportModule.java | 11 +- .../transport/ServerTransportFilter.java | 47 +++- .../n2n/IPFilteringN2NAuthenticator.java | 2 +- ...NettySecuredHttpServerTransportModule.java | 2 +- .../netty/NettySecuredTransportModule.java | 2 +- src/main/resources/es-plugin.properties | 2 +- .../MultipleIndicesPermissionsTests.java | 2 +- .../PermissionPrecedenceTests.java | 4 +- .../ScrollIdSigningTests.java | 2 +- .../shield/SecurityFilterTests.java | 215 ---------------- .../{plugin => }/ShieldPluginTests.java | 3 +- .../action/ShieldActionFilterTests.java | 105 ++++++++ .../shield/key/tool/SystemKeyToolTests.java | 2 +- .../shield/rest/ShieldRestFilterTests.java | 75 ++++++ .../shield/test/ShieldIntegrationTest.java | 4 +- .../transport/ClientTransportFilterTests.java | 38 +++ .../transport/ServerTransportFilterTests.java | 73 ++++++ .../elasticsearch/test/ShieldRestTests.java | 5 +- 55 files changed, 766 insertions(+), 670 deletions(-) delete mode 100644 src/main/java/org/elasticsearch/shield/SecurityFilter.java delete mode 100644 src/main/java/org/elasticsearch/shield/SecurityFilterModule.java rename src/main/java/org/elasticsearch/shield/{plugin => }/ShieldPlugin.java (90%) create mode 100644 src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java create mode 100644 src/main/java/org/elasticsearch/shield/action/ShieldActionModule.java create mode 100644 src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java create mode 100644 src/main/java/org/elasticsearch/shield/rest/ShieldRestModule.java rename src/main/java/org/elasticsearch/shield/{http => rest/action}/RestShieldInfoAction.java (97%) delete mode 100644 src/main/java/org/elasticsearch/shield/transport/SecuredRestModule.java rename src/test/java/org/elasticsearch/{shield => integration}/MultipleIndicesPermissionsTests.java (99%) rename src/test/java/org/elasticsearch/{shield => integration}/PermissionPrecedenceTests.java (97%) rename src/test/java/org/elasticsearch/{shield => integration}/ScrollIdSigningTests.java (99%) delete mode 100644 src/test/java/org/elasticsearch/shield/SecurityFilterTests.java rename src/test/java/org/elasticsearch/shield/{plugin => }/ShieldPluginTests.java (97%) create mode 100644 src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java create mode 100644 src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java create mode 100644 src/test/java/org/elasticsearch/shield/transport/ClientTransportFilterTests.java create mode 100644 src/test/java/org/elasticsearch/shield/transport/ServerTransportFilterTests.java diff --git a/src/main/java/org/elasticsearch/shield/SecurityFilter.java b/src/main/java/org/elasticsearch/shield/SecurityFilter.java deleted file mode 100644 index fb6882f3f70..00000000000 --- a/src/main/java/org/elasticsearch/shield/SecurityFilter.java +++ /dev/null @@ -1,233 +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; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.action.search.ClearScrollRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.search.SearchScrollRequest; -import org.elasticsearch.action.support.ActionFilter; -import org.elasticsearch.action.support.ActionFilterChain; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; -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.authz.AuthorizationException; -import org.elasticsearch.shield.authz.AuthorizationService; -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; - -import java.util.ArrayList; -import java.util.List; - -/** - * - */ -public class SecurityFilter extends AbstractComponent { - - private final AuthenticationService authcService; - private final AuthorizationService authzService; - private final KeyService keyService; - private final AuditTrail auditTrail; - - @Inject - public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, KeyService keyService, AuditTrail auditTrail) { - super(settings); - this.authcService = authcService; - this.authzService = authzService; - this.keyService = keyService; - this.auditTrail = auditTrail; - } - - 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) { - return authcService.authenticate(request); - } - - Request unsign(User user, String action, Request request) { - - try { - - if (request instanceof SearchScrollRequest) { - SearchScrollRequest scrollRequest = (SearchScrollRequest) request; - String scrollId = scrollRequest.scrollId(); - scrollRequest.scrollId(keyService.unsignAndVerify(scrollId)); - return request; - } - - if (request instanceof ClearScrollRequest) { - ClearScrollRequest clearScrollRequest = (ClearScrollRequest) request; - List signedIds = clearScrollRequest.scrollIds(); - List unsignedIds = new ArrayList<>(signedIds.size()); - for (String signedId : signedIds) { - unsignedIds.add(keyService.unsignAndVerify(signedId)); - } - clearScrollRequest.scrollIds(unsignedIds); - return request; - } - - return request; - - } catch (SignatureException se) { - auditTrail.tamperedRequest(user, action, request); - throw new AuthorizationException("Invalid request: " + se.getMessage()); - } - } - - Response sign(User user, String action, Response response) { - - if (response instanceof SearchResponse) { - SearchResponse searchResponse = (SearchResponse) response; - String scrollId = searchResponse.getScrollId(); - if (scrollId != null && !keyService.signed(scrollId)) { - searchResponse.scrollId(keyService.sign(scrollId)); - } - return response; - } - - return response; - } - - public static class Rest extends RestFilter { - - private final SecurityFilter filter; - - @Inject - public Rest(SecurityFilter filter, RestController controller) { - this.filter = filter; - controller.registerFilter(this); - } - - @Override - public int order() { - return Integer.MIN_VALUE; - } - - @Override - public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception { - - // CORS - allow for preflight unauthenticated OPTIONS request - if (request.method() != RestRequest.Method.OPTIONS) { - filter.authenticate(request); - } - - filterChain.continueProcessing(request, channel); - } - } - - public static class ServerTransport implements ServerTransportFilter { - - private final SecurityFilter filter; - - @Inject - public ServerTransport(SecurityFilter filter) { - this.filter = filter; - } - - @Override - public void inbound(String action, TransportRequest 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); - } - } - - public static class Action implements ActionFilter { - - private final SecurityFilter filter; - - @Inject - public Action(SecurityFilter filter) { - this.filter = filter; - } - - @Override - public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) { - try { - /** - 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) { - listener.onFailure(t); - } - } - - @Override - public void apply(String action, ActionResponse response, ActionListener listener, ActionFilterChain chain) { - chain.proceed(action, response, listener); - } - - @Override - public int order() { - return Integer.MIN_VALUE; - } - } - - static class SigningListener implements ActionListener { - - private final User user; - private final String action; - private final SecurityFilter filter; - private final ActionListener innerListener; - - private SigningListener(User user, String action, SecurityFilter filter, ActionListener innerListener) { - this.user = user; - this.action = action; - this.filter = filter; - this.innerListener = innerListener; - } - - @Override - public void onResponse(Response response) { - response = this.filter.sign(user, action, response); - innerListener.onResponse(response); - } - - @Override - public void onFailure(Throwable e) { - innerListener.onFailure(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/shield/SecurityFilterModule.java b/src/main/java/org/elasticsearch/shield/SecurityFilterModule.java deleted file mode 100644 index a436bc1c2af..00000000000 --- a/src/main/java/org/elasticsearch/shield/SecurityFilterModule.java +++ /dev/null @@ -1,24 +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; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.support.AbstractShieldModule; - -/** - * - */ -public class SecurityFilterModule extends AbstractShieldModule.Node { - - public SecurityFilterModule(Settings settings) { - super(settings); - } - - @Override - protected void configureNode() { - bind(SecurityFilter.class).asEagerSingleton(); - } -} diff --git a/src/main/java/org/elasticsearch/shield/ShieldException.java b/src/main/java/org/elasticsearch/shield/ShieldException.java index 4f1eb8ac8d8..afddd07b123 100644 --- a/src/main/java/org/elasticsearch/shield/ShieldException.java +++ b/src/main/java/org/elasticsearch/shield/ShieldException.java @@ -9,7 +9,6 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.collect.ImmutableMap; import org.elasticsearch.common.collect.Lists; -import org.elasticsearch.shield.plugin.ShieldPlugin; import java.util.List; diff --git a/src/main/java/org/elasticsearch/shield/ShieldModule.java b/src/main/java/org/elasticsearch/shield/ShieldModule.java index 4b5b58a5f92..a82085f3474 100644 --- a/src/main/java/org/elasticsearch/shield/ShieldModule.java +++ b/src/main/java/org/elasticsearch/shield/ShieldModule.java @@ -5,24 +5,23 @@ */ package org.elasticsearch.shield; -import org.elasticsearch.action.ActionModule; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.inject.Module; -import org.elasticsearch.common.inject.PreProcessModule; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.shield.action.ShieldActionModule; import org.elasticsearch.shield.audit.AuditTrailModule; import org.elasticsearch.shield.authc.AuthenticationModule; import org.elasticsearch.shield.authz.AuthorizationModule; import org.elasticsearch.shield.key.KeyModule; +import org.elasticsearch.shield.rest.ShieldRestModule; import org.elasticsearch.shield.ssl.SSLModule; import org.elasticsearch.shield.support.AbstractShieldModule; -import org.elasticsearch.shield.transport.SecuredRestModule; import org.elasticsearch.shield.transport.SecuredTransportModule; /** * */ -public class ShieldModule extends AbstractShieldModule.Spawn implements PreProcessModule { +public class ShieldModule extends AbstractShieldModule.Spawn { private final boolean enabled; @@ -31,13 +30,6 @@ public class ShieldModule extends AbstractShieldModule.Spawn implements PreProce this.enabled = settings.getAsBoolean("shield.enabled", true); } - @Override - public void processModule(Module module) { - if (module instanceof ActionModule && enabled && !clientMode) { - ((ActionModule) module).registerFilter(SecurityFilter.Action.class); - } - } - @Override public Iterable spawnModules(boolean clientMode) { // don't spawn modules if shield is explicitly disabled @@ -56,9 +48,9 @@ public class ShieldModule extends AbstractShieldModule.Spawn implements PreProce new AuthenticationModule(settings), new AuthorizationModule(settings), new AuditTrailModule(settings), + new ShieldRestModule(settings), + new ShieldActionModule(settings), new SecuredTransportModule(settings), - new SecuredRestModule(settings), - new SecurityFilterModule(settings), new KeyModule(settings), new SSLModule(settings)); } diff --git a/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java b/src/main/java/org/elasticsearch/shield/ShieldPlugin.java similarity index 90% rename from src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java rename to src/main/java/org/elasticsearch/shield/ShieldPlugin.java index d36f5b26bd1..898ee1c7a6e 100644 --- a/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java +++ b/src/main/java/org/elasticsearch/shield/ShieldPlugin.java @@ -3,7 +3,7 @@ * 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.plugin; +package org.elasticsearch.shield; import org.elasticsearch.client.support.Headers; import org.elasticsearch.common.collect.ImmutableList; @@ -12,12 +12,10 @@ import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.AbstractPlugin; -import org.elasticsearch.rest.RestModule; import org.elasticsearch.shield.ShieldModule; import org.elasticsearch.shield.ShieldSettingsException; import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.UsernamePasswordToken; -import org.elasticsearch.shield.http.RestShieldInfoAction; import java.io.File; import java.nio.file.Path; @@ -71,11 +69,6 @@ public class ShieldPlugin extends AbstractPlugin { .put(setting, UsernamePasswordToken.basicAuthHeaderValue(username, new SecuredString(password.toCharArray()))).build(); } - - public void onModule(RestModule restModule) { - restModule.addRestAction(RestShieldInfoAction.class); - } - public static Path configDir(Environment env) { return new File(env.configFile(), NAME).toPath(); } diff --git a/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java b/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java new file mode 100644 index 00000000000..a92a53038e9 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java @@ -0,0 +1,143 @@ +/* + * 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.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.search.ClearScrollRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.ActionFilter; +import org.elasticsearch.action.support.ActionFilterChain; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.audit.AuditTrail; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.shield.authz.AuthorizationException; +import org.elasticsearch.shield.authz.AuthorizationService; +import org.elasticsearch.shield.key.KeyService; +import org.elasticsearch.shield.key.SignatureException; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class ShieldActionFilter implements ActionFilter { + + private final AuthenticationService authcService; + private final AuthorizationService authzService; + private final KeyService keyService; + private final AuditTrail auditTrail; + + @Inject + public ShieldActionFilter(AuthenticationService authcService, AuthorizationService authzService, KeyService keyService, AuditTrail auditTrail) { + this.authcService = authcService; + this.authzService = authzService; + this.keyService = keyService; + this.auditTrail = auditTrail; + } + + @Override + public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) { + try { + /** + 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 = authcService.authenticate(action, request, User.SYSTEM); + authzService.authorize(user, action, request); + request = unsign(user, action, request); + chain.proceed(action, request, new SigningListener(this, listener)); + } catch (Throwable t) { + listener.onFailure(t); + } + } + + @Override + public void apply(String action, ActionResponse response, ActionListener listener, ActionFilterChain chain) { + chain.proceed(action, response, listener); + } + + @Override + public int order() { + return Integer.MIN_VALUE; + } + + Request unsign(User user, String action, Request request) { + + try { + + if (request instanceof SearchScrollRequest) { + SearchScrollRequest scrollRequest = (SearchScrollRequest) request; + String scrollId = scrollRequest.scrollId(); + scrollRequest.scrollId(keyService.unsignAndVerify(scrollId)); + return request; + } + + if (request instanceof ClearScrollRequest) { + ClearScrollRequest clearScrollRequest = (ClearScrollRequest) request; + List signedIds = clearScrollRequest.scrollIds(); + List unsignedIds = new ArrayList<>(signedIds.size()); + for (String signedId : signedIds) { + unsignedIds.add(keyService.unsignAndVerify(signedId)); + } + clearScrollRequest.scrollIds(unsignedIds); + return request; + } + + return request; + + } catch (SignatureException se) { + auditTrail.tamperedRequest(user, action, request); + throw new AuthorizationException("Invalid request: " + se.getMessage()); + } + } + + Response sign(Response response) { + + if (response instanceof SearchResponse) { + SearchResponse searchResponse = (SearchResponse) response; + String scrollId = searchResponse.getScrollId(); + if (scrollId != null && !keyService.signed(scrollId)) { + searchResponse.scrollId(keyService.sign(scrollId)); + } + return response; + } + + return response; + } + + static class SigningListener implements ActionListener { + + private final ShieldActionFilter filter; + private final ActionListener innerListener; + + private SigningListener(ShieldActionFilter filter, ActionListener innerListener) { + this.filter = filter; + this.innerListener = innerListener; + } + + @Override @SuppressWarnings("unchecked") + public void onResponse(Response response) { + response = this.filter.sign(response); + innerListener.onResponse(response); + } + + @Override + public void onFailure(Throwable e) { + innerListener.onFailure(e); + } + } +} diff --git a/src/main/java/org/elasticsearch/shield/action/ShieldActionModule.java b/src/main/java/org/elasticsearch/shield/action/ShieldActionModule.java new file mode 100644 index 00000000000..dda2db55a3f --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/action/ShieldActionModule.java @@ -0,0 +1,33 @@ +/* + * 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.action; + +import org.elasticsearch.action.ActionModule; +import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.inject.PreProcessModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.shield.support.AbstractShieldModule; + +/** + * + */ +public class ShieldActionModule extends AbstractShieldModule.Node implements PreProcessModule { + + public ShieldActionModule(Settings settings) { + super(settings); + } + + @Override + public void processModule(Module module) { + if (shieldEnabled && !clientMode && module instanceof ActionModule) { + ((ActionModule) module).registerFilter(ShieldActionFilter.class); + } + } + + @Override + protected void configureNode() { + } +} diff --git a/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java b/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java index 03d0e10636c..7f14aa90541 100644 --- a/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java +++ b/src/main/java/org/elasticsearch/shield/authc/AuthenticationModule.java @@ -26,8 +26,7 @@ public class AuthenticationModule extends AbstractShieldModule.Node.Spawn { return ImmutableList.of( new ESUsersModule(settings), new LdapModule(settings), - new ActiveDirectoryModule(settings) - ); + new ActiveDirectoryModule(settings)); } @Override diff --git a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java index 5cbaef2e177..3560f7979a7 100644 --- a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java +++ b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java @@ -55,6 +55,7 @@ public class InternalAuthenticationService extends AbstractComponent implements if (user == null) { throw new AuthenticationException("Unable to authenticate user for request"); } + request.putInContext(USER_KEY, user); return user; } @@ -123,13 +124,15 @@ public class InternalAuthenticationService extends AbstractComponent implements * the first realm that successfully authenticates will "win" and its authenticated user will be returned. * If none of the configured realms successfully authenticates the request, an {@link AuthenticationException} will * be thrown. - * + *

* The order by which the realms are checked is defined in {@link Realms}. * - * @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 + * @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 */ @@ -169,7 +172,6 @@ public class InternalAuthenticationService extends AbstractComponent implements if (realm.supports(token)) { User user = realm.authenticate(token); if (user != null) { - request.putInContext(USER_KEY, user); return user; } auditTrail.authenticationFailed(realm.type(), token, request); diff --git a/src/main/java/org/elasticsearch/shield/authc/Realm.java b/src/main/java/org/elasticsearch/shield/authc/Realm.java index 14125acce49..a93b7cd7251 100644 --- a/src/main/java/org/elasticsearch/shield/authc/Realm.java +++ b/src/main/java/org/elasticsearch/shield/authc/Realm.java @@ -21,7 +21,6 @@ public interface Realm { */ String type(); - /** * @return {@code true} if this realm supports the given authentication token, {@code false} otherwise. */ diff --git a/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryConnection.java b/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryConnection.java index 64a417084a9..33d449e9b90 100644 --- a/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryConnection.java +++ b/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryConnection.java @@ -8,7 +8,7 @@ package org.elasticsearch.shield.authc.active_directory; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.shield.authc.support.ldap.AbstractLdapConnection; import javax.naming.NamingEnumeration; @@ -20,7 +20,8 @@ import java.util.List; * An Ldap Connection customized for active directory. */ public class ActiveDirectoryConnection extends AbstractLdapConnection { - private static final ESLogger logger = ESLoggerFactory.getLogger(ActiveDirectoryConnection.class.getName()); + + private static final ESLogger logger = Loggers.getLogger(ActiveDirectoryConnection.class); private final String groupSearchDN; @@ -37,7 +38,7 @@ public class ActiveDirectoryConnection extends AbstractLdapConnection { * implemented properly so that it will clean up on garbage collection. */ @Override - public void close(){ + public void close() { try { jndiContext.close(); } catch (NamingException e) { @@ -98,9 +99,9 @@ public class ActiveDirectoryConnection extends AbstractLdapConnection { Attributes attrs = sr.getAttributes(); if (attrs != null) { - for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) { + for (NamingEnumeration ae = attrs.getAll(); ae.hasMore(); ) { Attribute attr = (Attribute) ae.next(); - for (NamingEnumeration e = attr.getAll(); e.hasMore();) { + for (NamingEnumeration e = attr.getAll(); e.hasMore(); ) { byte[] sid = (byte[]) e.next(); groupsSearchFilter.append("(objectSid="); groupsSearchFilter.append(binarySidToStringSid(sid)); @@ -125,9 +126,10 @@ public class ActiveDirectoryConnection extends AbstractLdapConnection { /** * To better understand what the sid is and how its string representation looks like, see * http://blogs.msdn.com/b/alextch/archive/2007/06/18/sample-java-application-that-retrieves-group-membership-of-an-active-directory-user-account.aspx + * * @param SID byte encoded security ID */ - static public String binarySidToStringSid( byte[] SID ) { + static public String binarySidToStringSid(byte[] SID) { String strSID; //convert the SID into string format @@ -142,20 +144,20 @@ public class ActiveDirectoryConnection extends AbstractLdapConnection { strSID = strSID + "-" + Long.toString(version); authority = SID[4]; - for (int i = 0;i<4;i++) { + for (int i = 0; i < 4; i++) { authority <<= 8; - authority += SID[4+i] & 0xFF; + authority += SID[4 + i] & 0xFF; } strSID = strSID + "-" + Long.toString(authority); count = SID[2]; count <<= 8; count += SID[1] & 0xFF; - for (int j=0;j sharedLdapEnv; private final String userSearchDN; private final String domainName; @Inject - public ActiveDirectoryConnectionFactory(Settings settings){ + public ActiveDirectoryConnectionFactory(Settings settings) { super(settings); domainName = componentSettings.get(AD_DOMAIN_NAME_SETTING); if (domainName == null) { @@ -50,7 +49,7 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen } userSearchDN = componentSettings.get(AD_USER_SEARCH_BASEDN_SETTING, buildDnFromDomain(domainName)); - String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING, new String[] { "ldaps://" + domainName + ":636"}); + String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING, new String[] { "ldaps://" + domainName + ":636" }); ImmutableMap.Builder builder = ImmutableMap.builder() .put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") @@ -65,6 +64,7 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen /** * This is an active directory bind that looks up the user DN after binding with a windows principal. + * * @param userName name of the windows user without the domain * @return An authenticated */ @@ -80,12 +80,12 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen DirContext ctx = new InitialDirContext(ldapEnv); SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - searchCtls.setReturningAttributes( Strings.EMPTY_ARRAY ); + searchCtls.setReturningAttributes(Strings.EMPTY_ARRAY); String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; - NamingEnumeration results = ctx.search(userSearchDN, searchFilter, new Object[]{ userPrincipal }, searchCtls); + NamingEnumeration results = ctx.search(userSearchDN, searchFilter, new Object[] { userPrincipal }, searchCtls); - if (results.hasMore()){ + if (results.hasMore()) { SearchResult entry = results.next(); String name = entry.getNameInNamespace(); @@ -98,7 +98,7 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen throw new ActiveDirectoryException("Search for user [" + userName + "], search root [" + userSearchDN + "] yielded no results"); } catch (NamingException e) { - throw new ActiveDirectoryException("Unable to authenticate user [" + userName + "] to active directory domain ["+ domainName +"]", e); + throw new ActiveDirectoryException("Unable to authenticate user [" + userName + "] to active directory domain [" + domainName + "]", e); } } diff --git a/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryGroupToRoleMapper.java b/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryGroupToRoleMapper.java index b2d30fa5d7b..effc84d15f3 100644 --- a/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryGroupToRoleMapper.java +++ b/src/main/java/org/elasticsearch/shield/authc/active_directory/ActiveDirectoryGroupToRoleMapper.java @@ -15,6 +15,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; * LDAP Group to role mapper specific to the "shield.authc.ldap" package */ public class ActiveDirectoryGroupToRoleMapper extends AbstractGroupToRoleMapper { + @Inject public ActiveDirectoryGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) { super(settings, ActiveDirectoryRealm.type, env, watcherService, null); diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserPasswdStore.java b/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserPasswdStore.java index 807db143922..7c8ad55fa9d 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserPasswdStore.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserPasswdStore.java @@ -17,7 +17,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.shield.authc.support.Hasher; import org.elasticsearch.shield.authc.support.RefreshListener; import org.elasticsearch.shield.authc.support.SecuredString; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserRolesStore.java b/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserRolesStore.java index 0f527fcaf48..8d09f71125e 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserRolesStore.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/FileUserRolesStore.java @@ -16,7 +16,7 @@ import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.shield.authc.support.RefreshListener; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnection.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnection.java index 3ddd3470ed2..5a907aa4031 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnection.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnection.java @@ -7,7 +7,7 @@ package org.elasticsearch.shield.authc.ldap; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.shield.authc.support.ldap.AbstractLdapConnection; import javax.naming.NamingEnumeration; @@ -19,17 +19,17 @@ import java.util.List; /** * Encapsulates jndi/ldap functionality into one authenticated connection. The constructor is package scoped, assuming * instances of this connection will be produced by the LdapConnectionFactory.open() methods. - * + *

* A standard looking usage pattern could look like this: -

-     try (LdapConnection session = ldapFac.bindXXX(...);
-     ...do stuff with the session
-     }
-     
+ *
+ * try (LdapConnection session = ldapFac.bindXXX(...);
+ * ...do stuff with the session
+ * }
+ * 
*/ public class LdapConnection extends AbstractLdapConnection { - private static final ESLogger logger = ESLoggerFactory.getLogger(LdapConnection.class.getName()); + private static final ESLogger logger = Loggers.getLogger(LdapConnection.class); private final String groupSearchDN; private final boolean isGroupSubTreeSearch; @@ -48,10 +48,11 @@ public class LdapConnection extends AbstractLdapConnection { /** * Fetches the groups that the user is a member of + * * @return List of group membership */ @Override - public List groups(){ + public List groups() { List groups = isFindGroupsByAttribute ? getGroupsFromUserAttrs(bindDn) : getGroupsFromSearch(bindDn); if (logger.isDebugEnabled()) { logger.debug("Found these groups [{}] for userDN [{}]", groups, this.bindDn); @@ -62,14 +63,15 @@ public class LdapConnection extends AbstractLdapConnection { /** * Fetches the groups of a user by doing a search. This could be abstracted out into a strategy class or through * an inherited class (with groups as the template method). + * * @param userDn user fully distinguished name to fetch group membership for * @return fully distinguished names of the roles */ - public List getGroupsFromSearch(String userDn){ + public List getGroupsFromSearch(String userDn) { List groups = new LinkedList<>(); SearchControls search = new SearchControls(); - search.setReturningAttributes( Strings.EMPTY_ARRAY ); - search.setSearchScope( this.isGroupSubTreeSearch ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE); + search.setReturningAttributes(Strings.EMPTY_ARRAY); + search.setSearchScope(this.isGroupSubTreeSearch ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE); //This could be made could be made configurable but it should cover all cases String filter = "(&" + @@ -77,9 +79,8 @@ public class LdapConnection extends AbstractLdapConnection { "(|(uniqueMember={0})(member={0})))"; try { - NamingEnumeration results = jndiContext.search( - groupSearchDN, filter, new Object[]{userDn}, search); - while (results.hasMoreElements()){ + NamingEnumeration results = jndiContext.search(groupSearchDN, filter, new Object[] { userDn }, search); + while (results.hasMoreElements()) { groups.add(results.next().getNameInNamespace()); } } catch (NamingException e) { @@ -91,16 +92,17 @@ public class LdapConnection extends AbstractLdapConnection { /** * Fetches the groups from the user attributes (if supported). This method could later be abstracted out * into a strategy class + * * @param userDn User fully distinguished name to fetch group membership from * @return list of groups the user is a member of. */ public List getGroupsFromUserAttrs(String userDn) { List groupDns = new LinkedList<>(); try { - Attributes results = jndiContext.getAttributes(userDn, new String[]{groupAttribute}); - for(NamingEnumeration ae = results.getAll(); ae.hasMore();) { - Attribute attr = (Attribute)ae.next(); - for (NamingEnumeration attrEnum = attr.getAll(); attrEnum.hasMore();) { + Attributes results = jndiContext.getAttributes(userDn, new String[] { groupAttribute }); + for (NamingEnumeration ae = results.getAll(); ae.hasMore(); ) { + Attribute attr = (Attribute) ae.next(); + for (NamingEnumeration attrEnum = attr.getAll(); attrEnum.hasMore(); ) { Object val = attrEnum.next(); if (val instanceof String) { String stringVal = (String) val; diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnectionFactory.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnectionFactory.java index 8bc089ad882..1f561d8abd3 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnectionFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapConnectionFactory.java @@ -26,7 +26,7 @@ import java.util.Hashtable; /** * This factory creates LDAP connections via iterating through user templates. - * + *

* Note that even though there is a separate factory for Active Directory, this factory would work against AD. A template * for each user context would need to be supplied. */ @@ -70,6 +70,7 @@ public class LdapConnectionFactory extends AbstractComponent implements Connecti /** * This iterates through the configured user templates attempting to open. If all attempts fail, all exceptions * are combined into one Exception as nested exceptions. + * * @param username a relative name, Not a distinguished name, that will be inserted into the template. * @return authenticated exception */ @@ -90,7 +91,7 @@ public class LdapConnectionFactory extends AbstractComponent implements Connecti return new LdapConnection(ctx, dn, findGroupsByAttribute, groupSubTreeSearch, groupSearchDN); } catch (NamingException e) { - logger.warn("Failed ldap authentication with user template [{}], dn [{}]", e, template, dn ); + logger.warn("Failed ldap authentication with user template [{}], dn [{}]", e, template, dn); } } @@ -99,6 +100,7 @@ public class LdapConnectionFactory extends AbstractComponent implements Connecti /** * Securely escapes the username and inserts it into the template using MessageFormat + * * @param username username to insert into the DN template. Any commas, equals or plus will be escaped. * @return DN (distinquished name) build from the template. */ diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapper.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapper.java index a7b366befb1..8895320d978 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapper.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapper.java @@ -15,6 +15,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; * LDAP Group to role mapper specific to the "shield.authc.ldap" package */ public class LdapGroupToRoleMapper extends AbstractGroupToRoleMapper { + @Inject public LdapGroupToRoleMapper(Settings settings, Environment env, ResourceWatcherService watcherService) { super(settings, LdapRealm.TYPE, env, watcherService, null); diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapRealm.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapRealm.java index d081ab0ea3b..1c8d2091367 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapRealm.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapRealm.java @@ -18,10 +18,7 @@ public class LdapRealm extends AbstractLdapRealm { public static final String TYPE = "ldap"; @Inject - public LdapRealm(Settings settings, - LdapConnectionFactory ldap, - LdapGroupToRoleMapper roleMapper, - RestController restController) { + public LdapRealm(Settings settings, LdapConnectionFactory ldap, LdapGroupToRoleMapper roleMapper, RestController restController) { super(settings, ldap, roleMapper, restController); } diff --git a/src/main/java/org/elasticsearch/shield/authc/support/CharArrays.java b/src/main/java/org/elasticsearch/shield/authc/support/CharArrays.java index 6f9b5a02f29..52952b9ccca 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/CharArrays.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/CharArrays.java @@ -15,6 +15,7 @@ import java.util.Arrays; * Helper class similar to Arrays to handle conversions for Char arrays */ public class CharArrays { + static char[] utf8BytesToChars(byte[] utf8Bytes) { ByteBuffer byteBuffer = ByteBuffer.wrap(utf8Bytes); CharBuffer charBuffer = Charsets.UTF_8.decode(byteBuffer); @@ -23,10 +24,11 @@ public class CharArrays { charBuffer.clear(); return chars; } + /** * Like String.indexOf for for an array of chars */ - static int indexOf(char[] array, char ch){ + static int indexOf(char[] array, char ch) { for (int i = 0; (i < array.length); i++) { if (array[i] == ch) { return i; diff --git a/src/main/java/org/elasticsearch/shield/authc/support/SecuredString.java b/src/main/java/org/elasticsearch/shield/authc/support/SecuredString.java index f78c53239ef..fb58bd393c5 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/SecuredString.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/SecuredString.java @@ -14,7 +14,7 @@ import java.util.Arrays; * This is not a string but a CharSequence that can be cleared of its memory. Important for handling passwords. * * @NotThreadSafe There is a chance that the chars could be cleared while doing operations on the chars. - * + *

* TODO: dot net's SecureString implementation does some obfuscation of the password to prevent gleaming passwords * from memory dumps. (this is hard as dot net uses windows system crypto. Thats probably the reason java still doesn't have it) */ @@ -25,9 +25,10 @@ public class SecuredString implements CharSequence { /** * Note: the passed in chars are not duplicated, but used directly for performance/optimization. DO NOT * modify or clear the chars after it has been passed into this constructor. + * * @param chars */ - public SecuredString(char[] chars){ + public SecuredString(char[] chars) { this.chars = new char[chars.length]; System.arraycopy(chars, 0, this.chars, 0, chars.length); } @@ -36,7 +37,7 @@ public class SecuredString implements CharSequence { * This constructor is used internally for the concatenate method. It DOES duplicate the passed in array, unlike * the public constructor */ - private SecuredString(char[] chars, int start, int end){ + private SecuredString(char[] chars, int start, int end) { this.chars = new char[end - start]; System.arraycopy(chars, start, this.chars, 0, this.chars.length); } @@ -53,13 +54,12 @@ public class SecuredString implements CharSequence { if (!Arrays.equals(chars, that.chars)) return false; return true; - } - else if (o instanceof CharSequence) { - CharSequence that = (CharSequence) o; + } else if (o instanceof CharSequence) { + CharSequence that = (CharSequence) o; if (cleared) return false; if (chars.length != that.length()) return false; - for(int i=0; i < chars.length; i++){ + for (int i = 0; i < chars.length; i++) { if (chars[i] != that.charAt(i)) { return false; } @@ -79,9 +79,10 @@ public class SecuredString implements CharSequence { /** * Note: This is a dangerous call that exists for performance/optimization * DO NOT modify the array returned by this method. To clear the array call SecureString.clear(). + * * @return the internal characters that MUST NOT be cleared manually */ - public char[] internalChars(){ + public char[] internalChars() { throwIfCleared(); return chars; } @@ -89,7 +90,7 @@ public class SecuredString implements CharSequence { /** * @return utf8 encoded bytes */ - public byte[] utf8Bytes(){ + public byte[] utf8Bytes() { throwIfCleared(); return CharArrays.toUtf8Bytes(chars); } @@ -115,19 +116,19 @@ public class SecuredString implements CharSequence { /** * Manually clear the underlying array holding the characters */ - public void clear(){ + public void clear() { cleared = true; Arrays.fill(chars, (char) 0); } @Override - public void finalize() throws Throwable{ + public void finalize() throws Throwable { clear(); super.finalize(); } public int indexOf(char toFind) { - for(int i=0; i message, UsernamePasswordToken defaultToken) { String authStr = message.getHeader(BASIC_AUTH_HEADER); if (authStr == null) { diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractGroupToRoleMapper.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractGroupToRoleMapper.java index d702053195b..6fc74585e44 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractGroupToRoleMapper.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractGroupToRoleMapper.java @@ -14,7 +14,7 @@ import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.shield.authc.support.RefreshListener; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; @@ -46,12 +46,10 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent { private CopyOnWriteArrayList listeners; - protected AbstractGroupToRoleMapper(Settings settings, - String realmType, - Environment env, - ResourceWatcherService watcherService, - @Nullable RefreshListener listener) { + protected AbstractGroupToRoleMapper(Settings settings, String realmType, Environment env, + ResourceWatcherService watcherService, @Nullable RefreshListener listener) { super(settings); + this.realmType = realmType; useUnmappedGroupsAsRoles = componentSettings.getAsBoolean(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false); file = resolveFile(componentSettings, env); groupRoles = parseFile(file, logger, realmType); @@ -62,7 +60,6 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent { if (listener != null) { listeners.add(listener); } - this.realmType = realmType; } public synchronized void addListener(RefreshListener listener) { @@ -77,24 +74,24 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent { return Paths.get(location); } - public static ImmutableMap> parseFile(Path path, ESLogger logger, String realmType) { + public static ImmutableMap> parseFile(Path path, ESLogger logger, String realmType) { if (!Files.exists(path)) { return ImmutableMap.of(); } - try (FileInputStream in = new FileInputStream( path.toFile() )){ + try (FileInputStream in = new FileInputStream(path.toFile())) { Settings settings = ImmutableSettings.builder() .loadFromStream(path.toString(), in) .build(); Map> groupToRoles = new HashMap<>(); Set roles = settings.names(); - for(String role: roles){ - for(String ldapDN: settings.getAsArray(role)){ + for (String role : roles) { + for (String ldapDN : settings.getAsArray(role)) { try { LdapName group = new LdapName(ldapDN); Set groupRoles = groupToRoles.get(group); - if (groupRoles == null){ + if (groupRoles == null) { groupRoles = new HashSet<>(); groupToRoles.put(group, groupRoles); } @@ -117,7 +114,7 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent { */ public Set mapRoles(List groupDns) { Set roles = new HashSet<>(); - for(String groupDn: groupDns){ + for (String groupDn : groupDns) { LdapName groupLdapName = LdapUtils.ldapName(groupDn); if (this.groupRoles.containsKey(groupLdapName)) { roles.addAll(this.groupRoles.get(groupLdapName)); @@ -161,14 +158,4 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent { } } - static interface Listener { - - final Listener NOOP = new Listener() { - @Override - public void onRefresh() { - } - }; - - void onRefresh(); - } } diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractLdapRealm.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractLdapRealm.java index 5eb4c3b3726..160ee0d5ad0 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractLdapRealm.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/AbstractLdapRealm.java @@ -19,6 +19,8 @@ import org.elasticsearch.transport.TransportMessage; import java.util.List; import java.util.Set; +import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; + /** * Supporting class for JNDI-based Realms */ @@ -27,12 +29,13 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm imp protected final ConnectionFactory connectionFactory; protected final AbstractGroupToRoleMapper roleMapper; - protected AbstractLdapRealm(Settings settings, ConnectionFactory ldap, AbstractGroupToRoleMapper roleMapper, RestController restController) { + protected AbstractLdapRealm(Settings settings, ConnectionFactory connectionFactory, + AbstractGroupToRoleMapper roleMapper, RestController restController) { super(settings); - this.connectionFactory = ldap; + this.connectionFactory = connectionFactory; this.roleMapper = roleMapper; roleMapper.addListener(new Listener()); - restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER); + restController.registerRelevantHeaders(BASIC_AUTH_HEADER); } @Override @@ -46,6 +49,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm imp /** * Given a username and password, open to ldap, retrieve groups, map to roles and build the user. + * * @return User with elasticsearch roles */ @Override @@ -54,7 +58,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm imp List groupDNs = session.groups(); Set roles = roleMapper.mapRoles(groupDNs); return new User.Simple(token.principal(), roles.toArray(new String[roles.size()])); - } catch (ShieldException e){ + } catch (ShieldException e) { if (logger.isDebugEnabled()) { logger.debug("Authentication Failed for user [{}]", e, token.principal()); } diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java index 371b97df0ed..29c7cebc6bb 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java @@ -13,7 +13,8 @@ import org.elasticsearch.shield.authc.support.SecuredString; * * A standard looking usage pattern could look like this:

-    try (LdapConnection session = ldapFac.bindXXX(...);
+    ConnectionFactory factory = ...
+    try (LdapConnection session = factory.open(...)) {
         ...do stuff with the session
     }
  
@@ -23,8 +24,11 @@ public interface ConnectionFactory { static final String URLS_SETTING = "url"; /** - * Password authenticated open - * @param user name of the user to authenticate the connection with. + * Authenticates the given user and opens a new connection that bound to it (meaning, all operations + * under the returned connection will be executed on behalf of the authenticated user. + * + * @param user The name of the user to authenticate the connection with. + * @param password The password of the user */ AbstractLdapConnection open(String user, SecuredString password) ; diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapSslSocketFactory.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapSslSocketFactory.java index 2ff22959a68..db4090cc2ed 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapSslSocketFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapSslSocketFactory.java @@ -30,7 +30,7 @@ import static org.elasticsearch.common.collect.Iterables.all; * factory that is initiated by the settings constructor. JNDI uses reflection to call the getDefault() static method * then checks to make sure that the factory returned is an LdapSslSocketFactory. Because of this we have to wrap * the socket factory - * + *

* http://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html */ public class LdapSslSocketFactory extends SocketFactory { @@ -70,6 +70,7 @@ public class LdapSslSocketFactory extends SocketFactory { /** * This is invoked by JNDI and the returned SocketFactory must be an LdapSslSocketFactory object + * * @return a singleton instance of LdapSslSocketFactory set by calling the init static method. */ public static SocketFactory getDefault() { @@ -77,13 +78,9 @@ public class LdapSslSocketFactory extends SocketFactory { return instance; } - public static boolean initialized() { - return instance != null; - } - final private SocketFactory socketFactory; - private LdapSslSocketFactory(SocketFactory wrappedSocketFactory){ + private LdapSslSocketFactory(SocketFactory wrappedSocketFactory) { socketFactory = wrappedSocketFactory; } @@ -111,8 +108,9 @@ public class LdapSslSocketFactory extends SocketFactory { /** * If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder + * * @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing) - * @param builder set of jndi properties, that will + * @param builder set of jndi properties, that will * @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols. */ public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder builder) { diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapUtils.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapUtils.java index 6721c3eca0e..f46f9f7a3e6 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapUtils.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/LdapUtils.java @@ -10,7 +10,7 @@ import org.elasticsearch.ElasticsearchException; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; -public class LdapUtils { +public final class LdapUtils { private LdapUtils() { } diff --git a/src/main/java/org/elasticsearch/shield/authz/SystemRole.java b/src/main/java/org/elasticsearch/shield/authz/SystemRole.java index addd66e8be9..c43b25c44ad 100644 --- a/src/main/java/org/elasticsearch/shield/authz/SystemRole.java +++ b/src/main/java/org/elasticsearch/shield/authz/SystemRole.java @@ -15,6 +15,7 @@ public class SystemRole { public static final SystemRole INSTANCE = new SystemRole(); public static final String NAME = "__es_system_role"; + private static final Predicate PREDICATE = Privilege.SYSTEM.predicate(); private SystemRole() { diff --git a/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java b/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java index af2c8ac001c..bbfc605efc9 100644 --- a/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java +++ b/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java @@ -22,8 +22,8 @@ import org.elasticsearch.transport.TransportRequest; import java.util.*; /** -* -*/ + * + */ public class DefaultIndicesResolver implements IndicesResolver { private final AuthorizationService authzService; @@ -76,7 +76,7 @@ public class DefaultIndicesResolver implements IndicesResolver } throw new IndexMissingException(new Index(Arrays.toString(indicesRequest.indices()))); } - ((IndicesRequest.Replaceable)indicesRequest).indices(indices.toArray(new String[indices.size()])); + ((IndicesRequest.Replaceable) indicesRequest).indices(indices.toArray(new String[indices.size()])); return Sets.newHashSet(indices); } return Sets.newHashSet(explodeWildcards(indicesRequest, metaData)); diff --git a/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java b/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java index 3cfdb011ec4..3e40a96bf58 100644 --- a/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java +++ b/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java @@ -21,7 +21,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.authz.Permission; import org.elasticsearch.shield.authz.Privilege; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; @@ -182,7 +182,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore { return roles.build(); - } catch (YAMLException|IOException ioe) { + } catch (YAMLException | IOException ioe) { throw new ElasticsearchException("Failed to read roles file [" + path.toAbsolutePath() + "]", ioe); } } diff --git a/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java b/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java index 042a8c6a981..4064cb3ea23 100644 --- a/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java +++ b/src/main/java/org/elasticsearch/shield/key/InternalKeyService.java @@ -13,7 +13,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.shield.ShieldException; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; diff --git a/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java b/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java new file mode 100644 index 00000000000..656927686d9 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java @@ -0,0 +1,40 @@ +/* + * 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.rest; + +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.rest.*; +import org.elasticsearch.shield.authc.AuthenticationService; + +/** + * + */ +public class ShieldRestFilter extends RestFilter { + + private final AuthenticationService service; + + @Inject + public ShieldRestFilter(AuthenticationService service, RestController controller) { + this.service = service; + controller.registerFilter(this); + } + + @Override + public int order() { + return Integer.MIN_VALUE; + } + + @Override + public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception { + + // CORS - allow for preflight unauthenticated OPTIONS request + if (request.method() != RestRequest.Method.OPTIONS) { + service.authenticate(request); + } + + filterChain.continueProcessing(request, channel); + } +} diff --git a/src/main/java/org/elasticsearch/shield/rest/ShieldRestModule.java b/src/main/java/org/elasticsearch/shield/rest/ShieldRestModule.java new file mode 100644 index 00000000000..3b078174b8c --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/rest/ShieldRestModule.java @@ -0,0 +1,35 @@ +/* + * 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.rest; + +import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.inject.PreProcessModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestModule; +import org.elasticsearch.shield.rest.action.RestShieldInfoAction; +import org.elasticsearch.shield.support.AbstractShieldModule; + +/** + * + */ +public class ShieldRestModule extends AbstractShieldModule.Node implements PreProcessModule { + + public ShieldRestModule(Settings settings) { + super(settings); + } + + @Override + protected void configureNode() { + bind(ShieldRestFilter.class).asEagerSingleton(); + } + + @Override + public void processModule(Module module) { + if (module instanceof RestModule) { + ((RestModule) module).addRestAction(RestShieldInfoAction.class); + } + } +} diff --git a/src/main/java/org/elasticsearch/shield/http/RestShieldInfoAction.java b/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java similarity index 97% rename from src/main/java/org/elasticsearch/shield/http/RestShieldInfoAction.java rename to src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java index ec09a80d4fb..87082f174e0 100644 --- a/src/main/java/org/elasticsearch/shield/http/RestShieldInfoAction.java +++ b/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java @@ -3,7 +3,7 @@ * 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.http; +package org.elasticsearch.shield.rest.action; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/src/main/java/org/elasticsearch/shield/support/AbstractShieldModule.java b/src/main/java/org/elasticsearch/shield/support/AbstractShieldModule.java index ddfd19dc597..ee735aa18d0 100644 --- a/src/main/java/org/elasticsearch/shield/support/AbstractShieldModule.java +++ b/src/main/java/org/elasticsearch/shield/support/AbstractShieldModule.java @@ -18,10 +18,12 @@ public abstract class AbstractShieldModule extends AbstractModule { protected final Settings settings; protected final boolean clientMode; + protected final boolean shieldEnabled; public AbstractShieldModule(Settings settings) { this.settings = settings; this.clientMode = !"node".equals(settings.get(Client.CLIENT_TYPE_SETTING)); + this.shieldEnabled = settings.getAsBoolean("shield.enabled", true); } @Override diff --git a/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java b/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java index d54123a14a4..f4362d1a6c3 100644 --- a/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java +++ b/src/main/java/org/elasticsearch/shield/transport/ClientTransportFilter.java @@ -5,6 +5,9 @@ */ package org.elasticsearch.shield.transport; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.transport.TransportRequest; /** @@ -12,11 +15,6 @@ 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 @@ -24,4 +22,36 @@ public interface ClientTransportFilter { */ void outbound(String action, TransportRequest request); -} + /** + * The client transport filter that should be used in transport clients + */ + public static class Client implements ClientTransportFilter { + + @Override + public void outbound(String action, TransportRequest request) { + } + } + + /** + * The client transport filter that should be used in nodes + */ + public static class Node implements ClientTransportFilter { + + private final AuthenticationService authcService; + + @Inject + public Node(AuthenticationService authcService) { + this.authcService = authcService; + } + + @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. There cannot be a request outgoing from this + node that is not associated with a user. + */ + authcService.attachUserHeaderIfMissing(request, User.SYSTEM); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/shield/transport/SecuredRestModule.java b/src/main/java/org/elasticsearch/shield/transport/SecuredRestModule.java deleted file mode 100644 index de0bc3c3632..00000000000 --- a/src/main/java/org/elasticsearch/shield/transport/SecuredRestModule.java +++ /dev/null @@ -1,25 +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.transport; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.SecurityFilter; -import org.elasticsearch.shield.support.AbstractShieldModule; - -/** - * - */ -public class SecuredRestModule extends AbstractShieldModule.Node { - - public SecuredRestModule(Settings settings) { - super(settings); - } - - @Override - protected void configureNode() { - bind(SecurityFilter.Rest.class).asEagerSingleton(); - } -} diff --git a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java index be9c5f7fe0a..60460476652 100644 --- a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java +++ b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java @@ -9,8 +9,7 @@ import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.PreProcessModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.SecurityFilter; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.support.AbstractShieldModule; import org.elasticsearch.shield.transport.n2n.IPFilteringN2NAuthenticator; import org.elasticsearch.shield.transport.netty.NettySecuredHttpServerTransportModule; @@ -50,13 +49,13 @@ 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); + bind(ServerTransportFilter.class).to(ServerTransportFilter.Client.class).asEagerSingleton(); + bind(ClientTransportFilter.class).to(ClientTransportFilter.Client.class).asEagerSingleton(); return; } - bind(ServerTransportFilter.class).to(SecurityFilter.ServerTransport.class).asEagerSingleton(); - bind(ClientTransportFilter.class).to(SecurityFilter.ClientTransport.class).asEagerSingleton(); + bind(ServerTransportFilter.class).to(ServerTransportFilter.Node.class).asEagerSingleton(); + bind(ClientTransportFilter.class).to(ClientTransportFilter.Node.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/ServerTransportFilter.java b/src/main/java/org/elasticsearch/shield/transport/ServerTransportFilter.java index d9b50caa2fd..cfccd3dc481 100644 --- a/src/main/java/org/elasticsearch/shield/transport/ServerTransportFilter.java +++ b/src/main/java/org/elasticsearch/shield/transport/ServerTransportFilter.java @@ -5,15 +5,14 @@ */ package org.elasticsearch.shield.transport; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.transport.TransportRequest; public interface ServerTransportFilter { - static final ServerTransportFilter NOOP = new ServerTransportFilter() { - @Override - public void inbound(String action, TransportRequest request) {} - }; - /** * Called just after the given request was received by the transport. Any exception * thrown by this method will stop the request from being handled and the error will @@ -21,4 +20,42 @@ public interface ServerTransportFilter { */ void inbound(String action, TransportRequest request); + /** + * The server trasnport filter that should be used in transport clients + */ + public static class Client implements ServerTransportFilter { + + @Override + public void inbound(String action, TransportRequest request) { + } + } + + /** + * The server trasnport filter that should be used in nodes + */ + public static class Node implements ServerTransportFilter { + + private final AuthenticationService authcService; + private final AuthorizationService authzService; + + @Inject + public Node(AuthenticationService authcService, AuthorizationService authzService) { + this.authcService = authcService; + this.authzService = authzService; + } + + @Override + public void inbound(String action, TransportRequest 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) + We can make this assumption because in nodes we also have the + {@link ClientTransportFilter.Node} that makes sure all outgoing requsts + from all the nodes are attached with a user (either a serialize user + an authentication token + */ + User user = authcService.authenticate(action, request, null); + authzService.authorize(user, action, request); + } + } } diff --git a/src/main/java/org/elasticsearch/shield/transport/n2n/IPFilteringN2NAuthenticator.java b/src/main/java/org/elasticsearch/shield/transport/n2n/IPFilteringN2NAuthenticator.java index 5da09bead09..5ea6154359a 100644 --- a/src/main/java/org/elasticsearch/shield/transport/n2n/IPFilteringN2NAuthenticator.java +++ b/src/main/java/org/elasticsearch/shield/transport/n2n/IPFilteringN2NAuthenticator.java @@ -20,7 +20,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.yaml.YamlXContent; import org.elasticsearch.env.Environment; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.watcher.FileChangesListener; import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.ResourceWatcherService; diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransportModule.java b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransportModule.java index e6d1da193cc..94264e835a8 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransportModule.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransportModule.java @@ -9,7 +9,7 @@ import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.PreProcessModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.http.HttpServerModule; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.support.AbstractShieldModule; /** diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransportModule.java b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransportModule.java index 17071359140..488150863af 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransportModule.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransportModule.java @@ -8,7 +8,7 @@ package org.elasticsearch.shield.transport.netty; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.PreProcessModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.support.AbstractShieldModule; import org.elasticsearch.transport.TransportModule; diff --git a/src/main/resources/es-plugin.properties b/src/main/resources/es-plugin.properties index 498d3feb264..de165a60a10 100644 --- a/src/main/resources/es-plugin.properties +++ b/src/main/resources/es-plugin.properties @@ -1,2 +1,2 @@ -plugin=org.elasticsearch.shield.plugin.ShieldPlugin +plugin=org.elasticsearch.shield.ShieldPlugin version=${project.version} diff --git a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java b/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java similarity index 99% rename from src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java rename to src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java index c5d35489517..096c3447c3a 100644 --- a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java +++ b/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java @@ -3,7 +3,7 @@ * 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; +package org.elasticsearch.integration; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.MultiSearchResponse; diff --git a/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java b/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java similarity index 97% rename from src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java rename to src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java index e41ea274edf..7ed865f9202 100644 --- a/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java +++ b/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java @@ -3,7 +3,7 @@ * 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; +package org.elasticsearch.integration; import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse; @@ -12,11 +12,9 @@ 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; diff --git a/src/test/java/org/elasticsearch/shield/ScrollIdSigningTests.java b/src/test/java/org/elasticsearch/integration/ScrollIdSigningTests.java similarity index 99% rename from src/test/java/org/elasticsearch/shield/ScrollIdSigningTests.java rename to src/test/java/org/elasticsearch/integration/ScrollIdSigningTests.java index 37720bb4bbc..94d8d0a0641 100644 --- a/src/test/java/org/elasticsearch/shield/ScrollIdSigningTests.java +++ b/src/test/java/org/elasticsearch/integration/ScrollIdSigningTests.java @@ -3,7 +3,7 @@ * 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; +package org.elasticsearch.integration; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.index.IndexRequestBuilder; diff --git a/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java b/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java deleted file mode 100644 index 4f280a1e59c..00000000000 --- a/src/test/java/org/elasticsearch/shield/SecurityFilterTests.java +++ /dev/null @@ -1,215 +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; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.search.SearchScrollRequest; -import org.elasticsearch.action.support.ActionFilterChain; -import org.elasticsearch.common.settings.ImmutableSettings; -import org.elasticsearch.rest.RestChannel; -import org.elasticsearch.rest.RestController; -import org.elasticsearch.rest.RestFilterChain; -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.authz.AuthorizationException; -import org.elasticsearch.shield.authz.AuthorizationService; -import org.elasticsearch.shield.key.KeyService; -import org.elasticsearch.shield.key.SignatureException; -import org.elasticsearch.test.ElasticsearchTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.mockito.Mockito.*; - - -/** - * - */ -public class SecurityFilterTests extends ElasticsearchTestCase { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private SecurityFilter filter; - private AuthenticationService authcService; - private AuthorizationService authzService; - private RestController restController; - private KeyService keyService; - private AuditTrail auditTrail; - - @Before - public void init() throws Exception { - authcService = mock(AuthenticationService.class); - authzService = mock(AuthorizationService.class); - restController = mock(RestController.class); - keyService = mock(KeyService.class); - auditTrail = mock(AuditTrail.class); - filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService, keyService, auditTrail); - } - - @Test - public void testAuthenticateAndAuthorize() throws Exception { - TransportRequest request = new InternalRequest(); - User user = new User.Simple("_username", "r1"); - when(authcService.authenticate("_action", request, null)).thenReturn(user); - filter.authenticateAndAuthorize("_action", request, null); - verify(authzService).authorize(user, "_action", request); - } - - @Test - public void testAuthenticateAndAuthorize_InternalAction() throws Exception { - TransportRequest request = new InternalRequest(); - User user = new User.Simple("_username", "r1"); - 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 testAuthenticateAndAuthorize_AuthenticationFails_Authenticate() throws Exception { - thrown.expect(AuthenticationException.class); - thrown.expectMessage("failed authc"); - TransportRequest request = new InternalRequest(); - when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc")); - filter.authenticateAndAuthorize("_action", request, null); - } - - @Test - public void testProcess_Rest_AuthenticationFails_Authenticate() throws Exception { - thrown.expect(AuthenticationException.class); - thrown.expectMessage("failed authc"); - RestRequest request = mock(RestRequest.class); - when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc")); - filter.authenticate(request); - } - - @Test - public void testAuthenticateAndAuthorize_AuthenticationFails_NoToken() throws Exception { - thrown.expect(AuthenticationException.class); - thrown.expectMessage("failed authc"); - TransportRequest request = new InternalRequest(); - when(authcService.authenticate("_action", request, null)).thenThrow(new AuthenticationException("failed authc")); - filter.authenticateAndAuthorize("_action", request, null); - } - - @Test - public void testAuthenticateAndAuthorize_Rest_AuthenticationFails_NoToken() throws Exception { - thrown.expect(AuthenticationException.class); - thrown.expectMessage("failed authc"); - RestRequest request = mock(RestRequest.class); - when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc")); - filter.authenticate(request); - } - - - @Test - public void testAuthenticateAndAuthorize_AuthorizationFails() throws Exception { - thrown.expect(AuthorizationException.class); - thrown.expectMessage("failed authz"); - TransportRequest request = new InternalRequest(); - User user = new User.Simple("_username", "r1"); - when(authcService.authenticate("_action", request, null)).thenReturn(user); - doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request); - filter.authenticateAndAuthorize("_action", request, null); - } - - @Test - public void testTransport_InboundRequest() throws Exception { - filter = mock(SecurityFilter.class); - SecurityFilter.ServerTransport transport = new SecurityFilter.ServerTransport(filter); - InternalRequest request = new InternalRequest(); - transport.inbound("_action", request); - verify(filter).authenticateAndAuthorize("_action", request, null); - } - - @Test - public void testTransport_InboundRequest_Exception() throws Exception { - thrown.expect(RuntimeException.class); - thrown.expectMessage("process-error"); - 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, null); - transport.inbound("_action", request); - } - - @Test - public void testAction_Process() throws Exception { - filter = mock(SecurityFilter.class); - SecurityFilter.Action action = new SecurityFilter.Action(filter); - ActionRequest request = mock(ActionRequest.class); - ActionListener listener = mock(ActionListener.class); - 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, User.SYSTEM); - verify(chain).proceed(eq("_action"), eq(request), isA(SecurityFilter.SigningListener.class)); - } - - @Test - public void testAction_Process_Exception() throws Exception { - filter = mock(SecurityFilter.class); - SecurityFilter.Action action = new SecurityFilter.Action(filter); - ActionRequest request = mock(ActionRequest.class); - ActionListener listener = mock(ActionListener.class); - ActionFilterChain chain = mock(ActionFilterChain.class); - RuntimeException exception = new RuntimeException("process-error"); - doThrow(exception).when(filter).authenticateAndAuthorize("_action", request, User.SYSTEM); - action.apply("_action", request, listener, chain); - verify(listener).onFailure(exception); - verifyNoMoreInteractions(chain); - } - - @Test - public void testAction_SignatureError() throws Exception { - SecurityFilter.Action action = new SecurityFilter.Action(filter); - SearchScrollRequest request = new SearchScrollRequest("scroll_id"); - ActionListener listener = mock(ActionListener.class); - ActionFilterChain chain = mock(ActionFilterChain.class); - SignatureException sigException = new SignatureException("bad bad boy"); - User user = mock(User.class); - 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); - verify(listener).onFailure(isA(AuthorizationException.class)); - verify(auditTrail).tamperedRequest(user, "_action", request); - verifyNoMoreInteractions(chain); - } - - @Test - public void testRest_WithToken() throws Exception { - SecurityFilter.Rest rest = new SecurityFilter.Rest(filter, restController); - RestRequest request = mock(RestRequest.class); - RestChannel channel = mock(RestChannel.class); - RestFilterChain chain = mock(RestFilterChain.class); - rest.process(request, channel, chain); - verify(restController).registerFilter(rest); - } - - @Test - public void testRest_WithoutToken() throws Exception { - AuthenticationException exception = new AuthenticationException("no token"); - thrown.expect(AuthenticationException.class); - thrown.expectMessage("no token"); - SecurityFilter.Rest rest = new SecurityFilter.Rest(filter, restController); - RestRequest request = mock(RestRequest.class); - RestChannel channel = mock(RestChannel.class); - RestFilterChain chain = mock(RestFilterChain.class); - doThrow(exception).when(authcService).authenticate(request); - rest.process(request, channel, chain); - verify(restController).registerFilter(rest); - } - - private static class InternalRequest extends TransportRequest { - } -} diff --git a/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java b/src/test/java/org/elasticsearch/shield/ShieldPluginTests.java similarity index 97% rename from src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java rename to src/test/java/org/elasticsearch/shield/ShieldPluginTests.java index 8b10d350c74..7fa6f4049b9 100644 --- a/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java +++ b/src/test/java/org/elasticsearch/shield/ShieldPluginTests.java @@ -3,7 +3,7 @@ * 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.plugin; +package org.elasticsearch.shield; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; @@ -12,6 +12,7 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.internal.InternalNode; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.test.ShieldIntegrationTest; import org.elasticsearch.test.rest.client.http.HttpRequestBuilder; diff --git a/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java b/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java new file mode 100644 index 00000000000..4a7fb065e81 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java @@ -0,0 +1,105 @@ +/* + * 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.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.ActionFilterChain; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.audit.AuditTrail; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.shield.authz.AuthorizationException; +import org.elasticsearch.shield.authz.AuthorizationService; +import org.elasticsearch.shield.key.KeyService; +import org.elasticsearch.shield.key.SignatureException; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.*; + +/** + * + */ +public class ShieldActionFilterTests extends ElasticsearchTestCase { + + private AuthenticationService authcService; + private AuthorizationService authzService; + private KeyService keyService; + private AuditTrail auditTrail; + private ShieldActionFilter filter; + + @Before + public void init() throws Exception { + authcService = mock(AuthenticationService.class); + authzService = mock(AuthorizationService.class); + keyService = mock(KeyService.class); + auditTrail = mock(AuditTrail.class); + filter = new ShieldActionFilter(authcService, authzService, keyService, auditTrail); + } + + @Test + public void testApply() throws Exception { + ActionRequest request = mock(ActionRequest.class); + ActionListener listener = mock(ActionListener.class); + ActionFilterChain chain = mock(ActionFilterChain.class); + User user = new User.Simple("username", "r1", "r2"); + when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user); + doReturn(request).when(spy(filter)).unsign(user, "_action", request); + filter.apply("_action", request, listener, chain); + verify(authzService).authorize(user, "_action", request); + verify(chain).proceed(eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class)); + } + + @Test + public void testAction_Process_Exception() throws Exception { + ActionRequest request = mock(ActionRequest.class); + ActionListener listener = mock(ActionListener.class); + ActionFilterChain chain = mock(ActionFilterChain.class); + RuntimeException exception = new RuntimeException("process-error"); + User user = new User.Simple("username", "r1", "r2"); + when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user); + doThrow(exception).when(authzService).authorize(user, "_action", request); + filter.apply("_action", request, listener, chain); + verify(listener).onFailure(exception); + verifyNoMoreInteractions(chain); + } + + @Test + public void testAction_Signature() throws Exception { + SearchScrollRequest request = new SearchScrollRequest("signed_scroll_id"); + ActionListener listener = mock(ActionListener.class); + ActionFilterChain chain = mock(ActionFilterChain.class); + User user = mock(User.class); + when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user); + when(keyService.signed("signed_scroll_id")).thenReturn(true); + when(keyService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id"); + filter.apply("_action", request, listener, chain); + assertThat(request.scrollId(), equalTo("scroll_id")); + verify(authzService).authorize(user, "_action", request); + verify(chain).proceed(eq("_action"), eq(request), isA(ShieldActionFilter.SigningListener.class)); + } + + @Test + public void testAction_SignatureError() throws Exception { + SearchScrollRequest request = new SearchScrollRequest("scroll_id"); + ActionListener listener = mock(ActionListener.class); + ActionFilterChain chain = mock(ActionFilterChain.class); + SignatureException sigException = new SignatureException("bad bad boy"); + User user = mock(User.class); + when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user); + when(keyService.signed("scroll_id")).thenReturn(true); + doThrow(sigException).when(keyService).unsignAndVerify("scroll_id"); + filter.apply("_action", request, listener, chain); + verify(listener).onFailure(isA(AuthorizationException.class)); + verify(auditTrail).tamperedRequest(user, "_action", request); + verifyNoMoreInteractions(chain); + } +} diff --git a/src/test/java/org/elasticsearch/shield/key/tool/SystemKeyToolTests.java b/src/test/java/org/elasticsearch/shield/key/tool/SystemKeyToolTests.java index d3e455767b3..8d126b12b50 100644 --- a/src/test/java/org/elasticsearch/shield/key/tool/SystemKeyToolTests.java +++ b/src/test/java/org/elasticsearch/shield/key/tool/SystemKeyToolTests.java @@ -13,7 +13,7 @@ import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.shield.key.InternalKeyService; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java b/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java new file mode 100644 index 00000000000..6d522e327e6 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java @@ -0,0 +1,75 @@ +/* + * 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.rest; + +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestFilterChain; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.authc.AuthenticationException; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; + +/** + * + */ +public class ShieldRestFilterTests extends ElasticsearchTestCase { + + private AuthenticationService authcService; + private RestChannel channel; + private RestFilterChain chain; + private ShieldRestFilter filter; + + @Before + public void init() throws Exception { + authcService = mock(AuthenticationService.class); + RestController restController = mock(RestController.class); + channel = mock(RestChannel.class); + chain = mock(RestFilterChain.class); + filter = new ShieldRestFilter(authcService, restController); + verify(restController).registerFilter(filter); + } + + @Test + public void testProcess() throws Exception { + RestRequest request = mock(RestRequest.class); + User user = new User.Simple("_user", "r1"); + when(authcService.authenticate(request)).thenReturn(user); + filter.process(request, channel, chain); + verify(chain).continueProcessing(request, channel); + verifyZeroInteractions(channel); + } + + @Test + public void testProcess_AuthenticationError() throws Exception { + RestRequest request = mock(RestRequest.class); + when(authcService.authenticate(request)).thenThrow(new AuthenticationException("failed authc")); + try { + filter.process(request, channel, chain); + fail("expected rest filter process to throw an authentication exception when authentication fails"); + } catch (AuthenticationException e) { + assertThat(e.getMessage(), equalTo("failed authc")); + } + verifyZeroInteractions(channel); + verifyZeroInteractions(chain); + } + + @Test + public void testProcess_OptionsMethod() throws Exception { + RestRequest request = mock(RestRequest.class); + when(request.method()).thenReturn(RestRequest.Method.OPTIONS); + filter.process(request, channel, chain); + verify(chain).continueProcessing(request, channel); + verifyZeroInteractions(channel); + verifyZeroInteractions(authcService); + } +} diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index 5c3d73f8d3f..94406478423 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -20,8 +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.ShieldPlugin; import org.elasticsearch.shield.transport.netty.NettySecuredTransport; import org.elasticsearch.test.ElasticsearchIntegrationTest; import org.elasticsearch.transport.Transport; @@ -35,7 +34,6 @@ 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; diff --git a/src/test/java/org/elasticsearch/shield/transport/ClientTransportFilterTests.java b/src/test/java/org/elasticsearch/shield/transport/ClientTransportFilterTests.java new file mode 100644 index 00000000000..e297cbf5102 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/transport/ClientTransportFilterTests.java @@ -0,0 +1,38 @@ +/* + * 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.shield.User; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * + */ +public class ClientTransportFilterTests extends ElasticsearchTestCase { + + private AuthenticationService authcService; + private ClientTransportFilter filter; + + @Before + public void init() throws Exception { + authcService = mock(AuthenticationService.class); + filter = new ClientTransportFilter.Node(authcService); + } + + @Test + public void testOutbound() throws Exception { + TransportRequest request = mock(TransportRequest.class); + filter.outbound("_action", request); + verify(authcService).attachUserHeaderIfMissing(request, User.SYSTEM); + } +} diff --git a/src/test/java/org/elasticsearch/shield/transport/ServerTransportFilterTests.java b/src/test/java/org/elasticsearch/shield/transport/ServerTransportFilterTests.java new file mode 100644 index 00000000000..cc30c3ce70d --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/transport/ServerTransportFilterTests.java @@ -0,0 +1,73 @@ +/* + * 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.shield.User; +import org.elasticsearch.shield.authc.AuthenticationException; +import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.shield.authz.AuthorizationException; +import org.elasticsearch.shield.authz.AuthorizationService; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.*; + +/** + * + */ +public class ServerTransportFilterTests extends ElasticsearchTestCase { + + private AuthenticationService authcService; + private AuthorizationService authzService; + private ServerTransportFilter filter; + + @Before + public void init() throws Exception { + authcService = mock(AuthenticationService.class); + authzService = mock(AuthorizationService.class); + filter = new ServerTransportFilter.Node(authcService, authzService); + } + + @Test + public void testInbound() throws Exception { + TransportRequest request = mock(TransportRequest.class); + User user = mock(User.class); + when(authcService.authenticate("_action", request, null)).thenReturn(user); + filter.inbound("_action", request); + verify(authzService).authorize(user, "_action", request); + } + + @Test + public void testInbound_AuthenticationException() throws Exception { + TransportRequest request = mock(TransportRequest.class); + doThrow(new AuthenticationException("authc failed")).when(authcService).authenticate("_action", request, null); + try { + filter.inbound("_action", request); + fail("expected filter inbound to throw an authentication exception on authentication error"); + } catch (AuthenticationException e) { + assertThat(e.getMessage(), equalTo("authc failed")); + } + verifyZeroInteractions(authzService); + } + + @Test + public void testInbound_AuthorizationException() throws Exception { + TransportRequest request = mock(TransportRequest.class); + User user = mock(User.class); + when(authcService.authenticate("_action", request, null)).thenReturn(user); + doThrow(new AuthorizationException("authz failed")).when(authzService).authorize(user, "_action", request); + try { + filter.inbound("_action", request); + fail("expected filter inbound to throw an authorization exception on authorization error"); + } catch (AuthorizationException e) { + assertThat(e.getMessage(), equalTo("authz failed")); + } + } + +} diff --git a/src/test/java/org/elasticsearch/test/ShieldRestTests.java b/src/test/java/org/elasticsearch/test/ShieldRestTests.java index cf4a560562f..fcf1ea2a9e5 100644 --- a/src/test/java/org/elasticsearch/test/ShieldRestTests.java +++ b/src/test/java/org/elasticsearch/test/ShieldRestTests.java @@ -18,7 +18,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.PluginsService; import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.key.InternalKeyService; -import org.elasticsearch.shield.plugin.ShieldPlugin; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.transport.netty.NettySecuredTransport; import org.elasticsearch.test.rest.ElasticsearchRestTests; import org.elasticsearch.test.rest.RestTestCandidate; @@ -53,7 +53,8 @@ public class ShieldRestTests extends ElasticsearchRestTests { public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n"; public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n"; public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_ROLE + ":" + DEFAULT_USER_NAME+ "\n"; - public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" + + public static final String CONFIG_ROLE_ALLOW_ALL = + DEFAULT_ROLE + ":\n" + " cluster: ALL\n" + " indices:\n" + " '*': ALL\n";