[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@d652041860
This commit is contained in:
uboness 2014-11-22 05:31:03 +01:00
parent 6087480368
commit 22eea8aba0
55 changed files with 766 additions and 670 deletions

View File

@ -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 extends ActionRequest> 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<String> signedIds = clearScrollRequest.scrollIds();
List<String> 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 extends ActionResponse> 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<Response extends ActionResponse> implements ActionListener<Response> {
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 extends ActionRequest> 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<String> signedIds = clearScrollRequest.scrollIds();
List<String> 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 extends ActionResponse> 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<Response extends ActionResponse> implements ActionListener<Response> {
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);
}
}
}

View File

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

View File

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

View File

@ -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.
*
* <p/>
* 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
*
* @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);

View File

@ -21,7 +21,6 @@ public interface Realm<T extends AuthenticationToken> {
*/
String type();
/**
* @return {@code true} if this realm supports the given authentication token, {@code false} otherwise.
*/

View File

@ -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;
@ -125,6 +126,7 @@ 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) {

View File

@ -35,7 +35,6 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
public static final String AD_DOMAIN_NAME_SETTING = "domain_name";
public static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search_dn";
static final String MODE_NAME = "active_directory";
private final ImmutableMap<String, Serializable> sharedLdapEnv;
private final String userSearchDN;
@ -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
*/

View File

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

View File

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

View File

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

View File

@ -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.
*
* <p/>
* A standard looking usage pattern could look like this:
<pre>
try (LdapConnection session = ldapFac.bindXXX(...);
...do stuff with the session
}
</pre>
* <pre>
* try (LdapConnection session = ldapFac.bindXXX(...);
* ...do stuff with the session
* }
* </pre>
*/
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,6 +48,7 @@ public class LdapConnection extends AbstractLdapConnection {
/**
* Fetches the groups that the user is a member of
*
* @return List of group membership
*/
@Override
@ -62,6 +63,7 @@ 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
*/
@ -77,8 +79,7 @@ public class LdapConnection extends AbstractLdapConnection {
"(|(uniqueMember={0})(member={0})))";
try {
NamingEnumeration<SearchResult> results = jndiContext.search(
groupSearchDN, filter, new Object[]{userDn}, search);
NamingEnumeration<SearchResult> results = jndiContext.search(groupSearchDN, filter, new Object[] { userDn }, search);
while (results.hasMoreElements()) {
groups.add(results.next().getNameInNamespace());
}
@ -91,6 +92,7 @@ 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.
*/

View File

@ -26,7 +26,7 @@ import java.util.Hashtable;
/**
* This factory creates LDAP connections via iterating through user templates.
*
* <p/>
* 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
*/
@ -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.
*/

View File

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

View File

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

View File

@ -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,6 +24,7 @@ public class CharArrays {
charBuffer.clear();
return chars;
}
/**
* Like String.indexOf for for an array of chars
*/

View File

@ -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.
*
* <p/>
* 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,6 +25,7 @@ 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) {
@ -53,8 +54,7 @@ public class SecuredString implements CharSequence {
if (!Arrays.equals(chars, that.chars)) return false;
return true;
}
else if (o instanceof CharSequence) {
} else if (o instanceof CharSequence) {
CharSequence that = (CharSequence) o;
if (cleared) return false;
if (chars.length != that.length()) return false;
@ -79,6 +79,7 @@ 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() {

View File

@ -67,7 +67,6 @@ public class UsernamePasswordToken implements AuthenticationToken {
}
public static UsernamePasswordToken extractToken(TransportMessage<?> message, UsernamePasswordToken defaultToken) {
String authStr = message.getHeader(BASIC_AUTH_HEADER);
if (authStr == null) {

View File

@ -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<RefreshListener> 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) {
@ -161,14 +158,4 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent {
}
}
static interface Listener {
final Listener NOOP = new Listener() {
@Override
public void onRefresh() {
}
};
void onRefresh();
}
}

View File

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

View File

@ -13,7 +13,8 @@ import org.elasticsearch.shield.authc.support.SecuredString;
*
* A standard looking usage pattern could look like this:
<pre>
try (LdapConnection session = ldapFac.bindXXX(...);
ConnectionFactory factory = ...
try (LdapConnection session = factory.open(...)) {
...do stuff with the session
}
</pre>
@ -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) ;

View File

@ -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
*
* <p/>
* 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,10 +78,6 @@ public class LdapSslSocketFactory extends SocketFactory {
return instance;
}
public static boolean initialized() {
return instance != null;
}
final private SocketFactory socketFactory;
private LdapSslSocketFactory(SocketFactory wrappedSocketFactory) {
@ -111,6 +108,7 @@ 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
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.

View File

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

View File

@ -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<String> PREDICATE = Privilege.SYSTEM.predicate();
private SystemRole() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1,2 @@
plugin=org.elasticsearch.shield.plugin.ShieldPlugin
plugin=org.elasticsearch.shield.ShieldPlugin
version=${project.version}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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