[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:
parent
6087480368
commit
22eea8aba0
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
* @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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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<count;j++) {
|
||||
rid = SID[11 + (j*4)] & 0xFF;
|
||||
for (int k=1;k<4;k++) {
|
||||
for (int j = 0; j < count; j++) {
|
||||
rid = SID[11 + (j * 4)] & 0xFF;
|
||||
for (int k = 1; k < 4; k++) {
|
||||
rid <<= 8;
|
||||
rid += SID[11-k + (j*4)] & 0xFF;
|
||||
rid += SID[11 - k + (j * 4)] & 0xFF;
|
||||
}
|
||||
strSID = strSID + "-" + Long.toString(rid);
|
||||
}
|
||||
|
|
|
@ -35,14 +35,13 @@ 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;
|
||||
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<String, Serializable> builder = ImmutableMap.<String, Serializable>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<SearchResult> results = ctx.search(userSearchDN, searchFilter, new Object[]{ userPrincipal }, searchCtls);
|
||||
NamingEnumeration<SearchResult> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,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<String> groups(){
|
||||
public List<String> groups() {
|
||||
List<String> 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<String> getGroupsFromSearch(String userDn){
|
||||
public List<String> getGroupsFromSearch(String userDn) {
|
||||
List<String> 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<SearchResult> results = jndiContext.search(
|
||||
groupSearchDN, filter, new Object[]{userDn}, search);
|
||||
while (results.hasMoreElements()){
|
||||
NamingEnumeration<SearchResult> 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<String> getGroupsFromUserAttrs(String userDn) {
|
||||
List<String> 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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,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<chars.length; i++){
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
if (chars[i] == toFind) {
|
||||
return i;
|
||||
}
|
||||
|
@ -139,13 +140,13 @@ public class SecuredString implements CharSequence {
|
|||
* @param toAppend String to combine with this SecureString
|
||||
* @return a new SecureString with toAppend concatenated
|
||||
*/
|
||||
public SecuredString concat(CharSequence toAppend ) {
|
||||
public SecuredString concat(CharSequence toAppend) {
|
||||
throwIfCleared();
|
||||
|
||||
CharBuffer buffer = CharBuffer.allocate(chars.length+toAppend.length());
|
||||
CharBuffer buffer = CharBuffer.allocate(chars.length + toAppend.length());
|
||||
buffer.put(chars);
|
||||
for(int i = 0; i < toAppend.length(); i++){
|
||||
buffer.put(i+chars.length, toAppend.charAt(i));
|
||||
for (int i = 0; i < toAppend.length(); i++) {
|
||||
buffer.put(i + chars.length, toAppend.charAt(i));
|
||||
}
|
||||
return new SecuredString(buffer.array());
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -77,24 +74,24 @@ public abstract class AbstractGroupToRoleMapper extends AbstractComponent {
|
|||
return Paths.get(location);
|
||||
}
|
||||
|
||||
public static ImmutableMap<LdapName, Set<String>> parseFile(Path path, ESLogger logger, String realmType) {
|
||||
public static ImmutableMap<LdapName, Set<String>> 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<LdapName, Set<String>> groupToRoles = new HashMap<>();
|
||||
Set<String> 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<String> 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<String> mapRoles(List<String> groupDns) {
|
||||
Set<String> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> groupDNs = session.groups();
|
||||
Set<String> 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());
|
||||
}
|
||||
|
|
|
@ -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) ;
|
||||
|
||||
|
|
|
@ -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,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<String, Serializable> builder) {
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -22,8 +22,8 @@ import org.elasticsearch.transport.TransportRequest;
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
public class DefaultIndicesResolver implements IndicesResolver<TransportRequest> {
|
||||
|
||||
private final AuthorizationService authzService;
|
||||
|
@ -76,7 +76,7 @@ public class DefaultIndicesResolver implements IndicesResolver<TransportRequest>
|
|||
}
|
||||
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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
plugin=org.elasticsearch.shield.plugin.ShieldPlugin
|
||||
plugin=org.elasticsearch.shield.ShieldPlugin
|
||||
version=${project.version}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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;
|
|
@ -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 {
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue