Added transport filtering
Requests are now auth'ed on the transport level as well (in addition to the transport action level). This is required as some internal requests are not executed as actions, thus not going through the auth process in the transport action. Since we have n2n authentication, we also assume here that requests that are not associated with an authentication token are internal system calls. We then, auth the request as a system user. Also Added a system realm (to handle system requests) Original commit: elastic/x-pack-elasticsearch@2c917318f0
This commit is contained in:
parent
452367b674
commit
9c55be1530
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.shield;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.collect.Lists;
|
||||
import org.elasticsearch.shield.plugin.SecurityPlugin;
|
||||
|
@ -17,7 +18,9 @@ import java.util.List;
|
|||
*/
|
||||
public class SecurityException extends ElasticsearchException.WithRestHeaders {
|
||||
|
||||
public static final ImmutableMap<String, List<String>> HEADERS = ImmutableMap.<String, List<String>>builder().put("WWW-Authenticate", Lists.newArrayList("Basic realm=\""+ SecurityPlugin.NAME +"\"")).build();
|
||||
public static final ImmutableMap<String, List<String>> HEADERS = ImmutableMap.<String, List<String>>builder()
|
||||
.put("WWW-Authenticate", Lists.newArrayList("Basic realm=\""+ SecurityPlugin.NAME +"\""))
|
||||
.build();
|
||||
|
||||
public SecurityException(String msg) {
|
||||
super(msg, HEADERS);
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.support.ActionFilterChain;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.shield.transport.TransportFilter;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SecurityFilter extends AbstractComponent {
|
||||
|
||||
private final AuthenticationService authcService;
|
||||
private final AuthorizationService authzService;
|
||||
|
||||
@Inject
|
||||
public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService) {
|
||||
super(settings);
|
||||
this.authcService = authcService;
|
||||
this.authzService = authzService;
|
||||
}
|
||||
|
||||
void process(String action, TransportRequest request, AuthenticationToken defaultToken) {
|
||||
AuthenticationToken token = authcService.token(action, request, defaultToken);
|
||||
User user = authcService.authenticate(action, request, token);
|
||||
authzService.authorize(user, action, request);
|
||||
}
|
||||
|
||||
public static class Transport extends TransportFilter.Base {
|
||||
|
||||
private final SecurityFilter filter;
|
||||
|
||||
@Inject
|
||||
public Transport(SecurityFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inboundRequest(String action, TransportRequest request) {
|
||||
filter.process(action, request, SystemRealm.TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Action implements org.elasticsearch.action.support.ActionFilter {
|
||||
|
||||
private final SecurityFilter filter;
|
||||
|
||||
@Inject
|
||||
public Action(SecurityFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) {
|
||||
try {
|
||||
filter.process(action, request, null);
|
||||
} catch (Throwable t) {
|
||||
listener.onFailure(t);
|
||||
return;
|
||||
}
|
||||
chain.continueProcessing(action, request, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,21 +5,23 @@
|
|||
*/
|
||||
package org.elasticsearch.shield;
|
||||
|
||||
import org.elasticsearch.action.ActionModule;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.inject.Modules;
|
||||
import org.elasticsearch.common.inject.PreProcessModule;
|
||||
import org.elasticsearch.common.inject.SpawnModules;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.audit.AuditTrailModule;
|
||||
import org.elasticsearch.shield.authc.AuthenticationModule;
|
||||
import org.elasticsearch.shield.authz.AuthorizationModule;
|
||||
import org.elasticsearch.shield.n2n.N2NModule;
|
||||
import org.elasticsearch.shield.transport.SecuredTransportModule;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SecurityModule extends AbstractModule implements SpawnModules {
|
||||
public class SecurityModule extends AbstractModule implements SpawnModules, PreProcessModule {
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
|
@ -27,6 +29,13 @@ public class SecurityModule extends AbstractModule implements SpawnModules {
|
|||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processModule(Module module) {
|
||||
if (module instanceof ActionModule) {
|
||||
((ActionModule) module).registerFilter(SecurityFilter.Action.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Module> spawnModules() {
|
||||
|
||||
|
@ -41,10 +50,11 @@ public class SecurityModule extends AbstractModule implements SpawnModules {
|
|||
}
|
||||
|
||||
return ImmutableList.of(
|
||||
Modules.createModule(AuthenticationModule.class, settings),
|
||||
Modules.createModule(AuthorizationModule.class, settings),
|
||||
Modules.createModule(AuditTrailModule.class, settings),
|
||||
Modules.createModule(N2NModule.class, settings));
|
||||
new AuthenticationModule(settings),
|
||||
new AuthorizationModule(),
|
||||
new AuditTrailModule(settings),
|
||||
new N2NModule(),
|
||||
new SecuredTransportModule(settings));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,11 +8,8 @@ package org.elasticsearch.shield;
|
|||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.lucene.Lucene;
|
||||
import org.elasticsearch.monitor.jvm.JvmInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
|
|
@ -25,6 +25,10 @@ public abstract class User {
|
|||
*/
|
||||
public abstract String[] roles();
|
||||
|
||||
public final boolean isSystem() {
|
||||
return this == SYSTEM;
|
||||
}
|
||||
|
||||
public static class Simple extends User {
|
||||
|
||||
private final String username;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package org.elasticsearch.shield.audit.logfile;
|
||||
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.name.Named;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
|
|
@ -8,11 +8,11 @@ package org.elasticsearch.shield.authc;
|
|||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.inject.Modules;
|
||||
import org.elasticsearch.common.inject.SpawnModules;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
|
||||
import org.elasticsearch.shield.authc.ldap.LdapModule;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -28,6 +28,7 @@ public class AuthenticationModule extends AbstractModule implements SpawnModules
|
|||
@Override
|
||||
public Iterable<? extends Module> spawnModules() {
|
||||
ImmutableList.Builder<? extends Module> modules = ImmutableList.builder();
|
||||
modules.add(new SystemRealm.Module());
|
||||
if (ESUsersModule.enabled(settings)) {
|
||||
modules.add(new ESUsersModule());
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ public interface AuthenticationService {
|
|||
|
||||
/**
|
||||
* Extracts the authenticate token from the given message. If no recognized auth token is associated
|
||||
* with the message, the given default token is returned.
|
||||
* with the message and the given defaultToken is not {@code null}, the default token will be returned.
|
||||
* Otherwise an AuthenticationException is thrown.
|
||||
*/
|
||||
AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken);
|
||||
|
||||
|
|
|
@ -15,9 +15,14 @@ import org.elasticsearch.transport.TransportMessage;
|
|||
|
||||
/**
|
||||
* An authentication service that delegates the authentication process to its configured {@link Realm realms}.
|
||||
* This service also supports request level caching of authenticated users (i.e. once a user authenticated
|
||||
* successfully, it is set on the request context to avoid subsequent redundant authentication process)
|
||||
*/
|
||||
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {
|
||||
|
||||
static final String TOKEN_CTX_KEY = "_shield_token";
|
||||
static final String USER_CTX_KEY = "_shield_user";
|
||||
|
||||
private final Realm[] realms;
|
||||
private final AuditTrail auditTrail;
|
||||
|
||||
|
@ -30,25 +35,32 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
|
||||
@Override
|
||||
public AuthenticationToken token(String action, TransportMessage<?> message) {
|
||||
AuthenticationToken token = token(action, message, null);
|
||||
if (token == null) {
|
||||
if (auditTrail != null) {
|
||||
auditTrail.anonymousAccess(action, message);
|
||||
}
|
||||
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
||||
}
|
||||
return token;
|
||||
return token(action, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken) {
|
||||
AuthenticationToken token = (AuthenticationToken) message.context().get(TOKEN_CTX_KEY);
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
for (Realm realm : realms) {
|
||||
AuthenticationToken token = realm.token(message);
|
||||
token = realm.token(message);
|
||||
if (token != null) {
|
||||
message.context().put(TOKEN_CTX_KEY, token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultToken == null) {
|
||||
if (auditTrail != null) {
|
||||
auditTrail.anonymousAccess(action, message);
|
||||
}
|
||||
throw new AuthenticationException("Missing authentication token for request [" + action + "]");
|
||||
}
|
||||
|
||||
message.context().put(TOKEN_CTX_KEY, defaultToken);
|
||||
return defaultToken;
|
||||
}
|
||||
|
||||
|
@ -72,10 +84,15 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
@SuppressWarnings("unchecked")
|
||||
public User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException {
|
||||
assert token != null : "cannot authenticate null tokens";
|
||||
User user = (User) message.context().get(USER_CTX_KEY);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
for (Realm realm : realms) {
|
||||
if (realm.supports(token)) {
|
||||
User user = realm.authenticate(token);
|
||||
user = realm.authenticate(token);
|
||||
if (user != null) {
|
||||
message.context().put(USER_CTX_KEY, user);
|
||||
return user;
|
||||
} else if (auditTrail != null) {
|
||||
auditTrail.authenticationFailed(realm.type(), token, action, message);
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||
import org.elasticsearch.shield.authc.ldap.LdapRealm;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -21,8 +22,9 @@ public class Realms {
|
|||
private final Realm[] realms;
|
||||
|
||||
@Inject
|
||||
public Realms(@Nullable ESUsersRealm esusers, @Nullable LdapRealm ldap) {
|
||||
public Realms(SystemRealm system, @Nullable ESUsersRealm esusers, @Nullable LdapRealm ldap) {
|
||||
List<Realm> realms = new ArrayList<>();
|
||||
realms.add(system);
|
||||
if (esusers != null) {
|
||||
realms.add(esusers);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.elasticsearch.shield.authc.support.UserPasswdStore;
|
|||
import org.elasticsearch.shield.authc.support.UserRolesStore;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
package org.elasticsearch.shield.authc.esusers;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.base.Charsets;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
|
|
@ -14,10 +14,7 @@ package org.elasticsearch.shield.authc.support;
|
|||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
|
|
|
@ -71,8 +71,12 @@ public class UsernamePasswordToken implements AuthenticationToken {
|
|||
}
|
||||
|
||||
public static void putTokenHeader(TransportRequest request, UsernamePasswordToken token) {
|
||||
String basicToken = token.username + ":" + new String(token.password);
|
||||
request.putHeader("Authorization", headerValue(token.username, token.password));
|
||||
}
|
||||
|
||||
public static String headerValue(String username, char[] passwd) {
|
||||
String basicToken = username + ":" + new String(passwd);
|
||||
basicToken = new String(Base64.encodeBase64(basicToken.getBytes(Charsets.UTF_8)), Charsets.UTF_8);
|
||||
request.putHeader("Authorization", "Basic " + basicToken);
|
||||
return "Basic " + basicToken;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.authc.system;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.Realm;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SystemRealm implements Realm<AuthenticationToken> {
|
||||
|
||||
public static final AuthenticationToken TOKEN = new AuthenticationToken() {
|
||||
@Override
|
||||
public String principal() {
|
||||
return "_system";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object credentials() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return "system";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationToken token(TransportMessage<?> message) {
|
||||
// as far as this realm is concerned, there's never a system token
|
||||
// in the request. The decision of whether a request is a system
|
||||
// request or not, is made elsewhere where the system token is
|
||||
// assumed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token == TOKEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(AuthenticationToken token) {
|
||||
return token == TOKEN ? User.SYSTEM : null;
|
||||
}
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(SystemRealm.class).asEagerSingleton();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,6 +69,17 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
|
||||
@Override
|
||||
public void authorize(User user, String action, TransportRequest request) throws AuthorizationException {
|
||||
|
||||
if (user.isSystem()) {
|
||||
MetaData metaData = clusterService.state().metaData();
|
||||
if (SystemRole.INSTANCE.check(action, request, metaData)) {
|
||||
grant(user, action, request);
|
||||
} else {
|
||||
deny(user, action, request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String[] roles = user.roles();
|
||||
if (roles.length == 0) {
|
||||
deny(user, action, request);
|
||||
|
@ -77,7 +88,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
MetaData metaData = clusterService.state().metaData();
|
||||
for (String role : roles) {
|
||||
Permission permission = rolesStore.permission(role);
|
||||
if (permission.check(action, request, metaData)) {
|
||||
if (permission != null && permission.check(action, request, metaData)) {
|
||||
grant(user, action, request);
|
||||
return;
|
||||
}
|
||||
|
@ -90,7 +101,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
if (auditTrail != null) {
|
||||
auditTrail.accessDenied(user, action, request);
|
||||
}
|
||||
throw new AuthorizationException("Action [" + action + "] is unauthorized");
|
||||
throw new AuthorizationException("Action [" + action + "] is unauthorized for user [" + user.principal() + "]");
|
||||
}
|
||||
|
||||
private void grant(User user, String action, TransportRequest request) {
|
||||
|
|
|
@ -10,14 +10,11 @@ import org.elasticsearch.action.IndicesRequest;
|
|||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.base.Predicate;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.Sets;
|
||||
import org.elasticsearch.shield.support.AutomatonPredicate;
|
||||
import org.elasticsearch.shield.support.Automatons;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a permission in the system. There are 3 types of permissions:
|
||||
|
@ -183,8 +180,12 @@ public interface Permission {
|
|||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
if (groups[i].check(action, request, metaData)) {
|
||||
boolean isIndicesRequest = request instanceof CompositeIndicesRequest || request instanceof IndicesRequest;
|
||||
if (!isIndicesRequest) {
|
||||
return false;
|
||||
}
|
||||
for (Group group : groups) {
|
||||
if (group.check(action, request, metaData)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -196,14 +197,14 @@ public interface Permission {
|
|||
private final Privilege.Index privilege;
|
||||
private final Predicate<String> actionMatcher;
|
||||
private final String[] indices;
|
||||
private final Predicate<String> indicesMatcher;
|
||||
private final Predicate<String> indexNameMatcher;
|
||||
|
||||
public Group(Privilege.Index privilege, String... indices) {
|
||||
assert indices.length != 0;
|
||||
this.privilege = privilege;
|
||||
this.actionMatcher = privilege.predicate();
|
||||
this.indices = indices;
|
||||
this.indicesMatcher = new AutomatonPredicate(Automatons.patterns(indices));
|
||||
this.indexNameMatcher = new AutomatonPredicate(Automatons.patterns(indices));
|
||||
}
|
||||
|
||||
public Privilege.Index privilege() {
|
||||
|
@ -217,34 +218,30 @@ public interface Permission {
|
|||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
|
||||
assert request instanceof IndicesRequest || request instanceof CompositeIndicesRequest :
|
||||
"the only requests passing the action matcher should be IndicesRequests";
|
||||
|
||||
if (!actionMatcher.apply(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isIndicesRequest = request instanceof CompositeIndicesRequest || request instanceof IndicesRequest;
|
||||
|
||||
assert isIndicesRequest : "the only requests passing the action matcher should be IndicesRequests";
|
||||
|
||||
// if for some reason we are missing an action... just for safety we'll reject
|
||||
if (!isIndicesRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> indices = Sets.newHashSet();
|
||||
if (request instanceof CompositeIndicesRequest) {
|
||||
CompositeIndicesRequest compositeIndicesRequest = (CompositeIndicesRequest) request;
|
||||
for (IndicesRequest indicesRequest : compositeIndicesRequest.subRequests()) {
|
||||
Collections.addAll(indices, explodeWildcards(indicesRequest, metaData));
|
||||
for (String index : explodeWildcards(indicesRequest, metaData)) {
|
||||
if (!indexNameMatcher.apply(index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Collections.addAll(indices, explodeWildcards((IndicesRequest) request, metaData));
|
||||
}
|
||||
|
||||
for (String index : indices) {
|
||||
if (!indicesMatcher.apply(index)) {
|
||||
for (String index : explodeWildcards((IndicesRequest) request, metaData)) {
|
||||
if (!indexNameMatcher.apply(index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ package org.elasticsearch.shield.authz;
|
|||
|
||||
import org.apache.lucene.util.automaton.Automaton;
|
||||
import org.apache.lucene.util.automaton.BasicAutomata;
|
||||
import org.apache.lucene.util.automaton.MinimizationOperations;
|
||||
import org.apache.lucene.util.automaton.RegExp;
|
||||
import org.elasticsearch.action.get.GetAction;
|
||||
import org.elasticsearch.action.search.SearchAction;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
@ -50,19 +48,17 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
return this.implies(other) && other.implies((P) this);
|
||||
}
|
||||
|
||||
static class Internal extends Privilege<Internal> {
|
||||
public static class Internal extends Privilege<Internal> {
|
||||
|
||||
protected final Automaton automaton;
|
||||
protected static final Predicate<String> PREDICATE = new AutomatonPredicate(Automatons.patterns("internal:.*"));
|
||||
|
||||
private Internal() {
|
||||
super(new Name("internal"));
|
||||
automaton = new RegExp("internal:.*", RegExp.ALL).toAutomaton();
|
||||
MinimizationOperations.minimize(automaton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<String> predicate() {
|
||||
return new AutomatonPredicate(automaton);
|
||||
return PREDICATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,7 +70,7 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
public static class Index extends AutomatonPrivilege<Index> {
|
||||
|
||||
public static final Index NONE = new Index(Name.NONE, BasicAutomata.makeEmpty());
|
||||
public static final Index ALL = new Index("all", "indices:.*");
|
||||
public static final Index ALL = new Index(Name.ALL, "indices:.*");
|
||||
public static final Index MANAGE = new Index("manage", "indices:monitor/.*", "indices:admin/.*");
|
||||
public static final Index MONITOR = new Index("monitor", "indices:monitor/.*");
|
||||
public static final Index DATA_ACCESS = new Index("data_access","indices:data/.*");
|
||||
|
@ -111,6 +107,10 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
super(name, patterns);
|
||||
}
|
||||
|
||||
private Index(Name name, String... patterns) {
|
||||
super(name, patterns);
|
||||
}
|
||||
|
||||
private Index(Name name, Automaton automaton) {
|
||||
super(name, automaton);
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
public static class Cluster extends AutomatonPrivilege<Cluster> {
|
||||
|
||||
public static final Cluster NONE = new Cluster(Name.NONE, BasicAutomata.makeEmpty());
|
||||
public static final Cluster ALL = new Cluster("all", "cluster:.*");
|
||||
public static final Cluster ALL = new Cluster(Name.ALL, "cluster:.*", "indices:admin/template/.*");
|
||||
public static final Cluster MONITOR = new Cluster("monitor", "cluster:monitor/.*");
|
||||
|
||||
private static final Cluster[] values = new Cluster[] { NONE, ALL, MONITOR };
|
||||
|
@ -184,6 +184,10 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
super(name, patterns);
|
||||
}
|
||||
|
||||
private Cluster(Name name, String... patterns) {
|
||||
super(name, patterns);
|
||||
}
|
||||
|
||||
private Cluster(Name name, Automaton automaton) {
|
||||
super(name, automaton);
|
||||
}
|
||||
|
@ -226,6 +230,11 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
this.automaton = Automatons.patterns(patterns);
|
||||
}
|
||||
|
||||
private AutomatonPrivilege(Name name, String... patterns) {
|
||||
super(name);
|
||||
this.automaton = Automatons.patterns(patterns);
|
||||
}
|
||||
|
||||
private AutomatonPrivilege(Name name, Automaton automaton) {
|
||||
super(name);
|
||||
this.automaton = automaton;
|
||||
|
@ -273,6 +282,7 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
public static class Name {
|
||||
|
||||
public static final Name NONE = new Name("none");
|
||||
public static final Name ALL = new Name("all");
|
||||
|
||||
private final ImmutableSet<String> parts;
|
||||
|
||||
|
|
|
@ -14,9 +14,14 @@ import org.elasticsearch.transport.TransportRequest;
|
|||
*/
|
||||
public class SystemRole extends Permission.Global {
|
||||
|
||||
public static final SystemRole INSTANCE = new SystemRole();
|
||||
|
||||
public static final String NAME = "__es_system_role";
|
||||
private static final Predicate<String> PREDICATE = Privilege.INTERNAL.predicate();
|
||||
|
||||
private SystemRole() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
return PREDICATE.apply(action);
|
||||
|
|
|
@ -17,7 +17,7 @@ public class AutomatonPredicate implements Predicate<String> {
|
|||
private final CharacterRunAutomaton automaton;
|
||||
|
||||
public AutomatonPredicate(Automaton automaton) {
|
||||
this.automaton = new CharacterRunAutomaton(automaton);
|
||||
this(new CharacterRunAutomaton(automaton));
|
||||
}
|
||||
|
||||
public AutomatonPredicate(CharacterRunAutomaton automaton) {
|
||||
|
|
|
@ -3,26 +3,27 @@
|
|||
* 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.ssl.netty;
|
||||
package org.elasticsearch.shield.transport;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.elasticsearch.shield.SecurityFilter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NettySSLTransportModule extends AbstractModule {
|
||||
public class SecuredTransportModule extends AbstractModule {
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
public NettySSLTransportModule(Settings settings) {
|
||||
public SecuredTransportModule(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(NettySSLTransport.class).asEagerSingleton();
|
||||
bind(Transport.class).to(NettySSLTransport.class);
|
||||
if (!settings.getAsBoolean("node.client", false)) {
|
||||
bind(TransportFilter.class).to(SecurityFilter.Transport.class).asEagerSingleton();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class SecuredTransportService extends TransportService {
|
||||
|
||||
private final TransportFilter filter;
|
||||
|
||||
@Inject
|
||||
public SecuredTransportService(Settings settings, Transport transport, ThreadPool threadPool, TransportFilter filter) {
|
||||
super(settings, transport, threadPool);
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public <T extends TransportResponse> void sendRequest(final DiscoveryNode node, final String action, final TransportRequest request,
|
||||
final TransportRequestOptions options, TransportResponseHandler<T> handler) {
|
||||
try {
|
||||
filter.outboundRequest(action, request);
|
||||
} catch (Throwable t) {
|
||||
handler.handleException(new TransportException("failed sending request", t));
|
||||
return;
|
||||
}
|
||||
super.sendRequest(node, action, request, options, new SecuredResponseHandler(handler, filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerHandler(String action, TransportRequestHandler handler) {
|
||||
super.registerHandler(action, new SecuredRequestHandler(action, handler, filter));
|
||||
}
|
||||
|
||||
static class SecuredRequestHandler implements TransportRequestHandler {
|
||||
|
||||
private final String action;
|
||||
private final TransportRequestHandler handler;
|
||||
private final TransportFilter filter;
|
||||
|
||||
SecuredRequestHandler(String action, TransportRequestHandler handler, TransportFilter filter) {
|
||||
this.action = action;
|
||||
this.handler = handler;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportRequest newInstance() {
|
||||
return handler.newInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(TransportRequest request, TransportChannel channel) throws Exception {
|
||||
try {
|
||||
filter.inboundRequest(action, request);
|
||||
} catch (Throwable t) {
|
||||
channel.sendResponse(t);
|
||||
return;
|
||||
}
|
||||
handler.messageReceived(request, new SecuredTransportChannel(channel, filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executor() {
|
||||
return handler.executor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceExecution() {
|
||||
return handler.isForceExecution();
|
||||
}
|
||||
}
|
||||
|
||||
static class SecuredResponseHandler implements TransportResponseHandler {
|
||||
|
||||
private final TransportResponseHandler handler;
|
||||
private final TransportFilter filter;
|
||||
|
||||
SecuredResponseHandler(TransportResponseHandler handler, TransportFilter filter) {
|
||||
this.handler = handler;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportResponse newInstance() {
|
||||
return handler.newInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(TransportResponse response) {
|
||||
try {
|
||||
filter.inboundResponse(response);
|
||||
} catch (Throwable t) {
|
||||
handleException(new TransportException("response received but rejected locally", t));
|
||||
return;
|
||||
}
|
||||
handler.handleResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(TransportException exp) {
|
||||
handler.handleException(exp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executor() {
|
||||
return handler.executor();
|
||||
}
|
||||
}
|
||||
|
||||
static class SecuredTransportChannel implements TransportChannel {
|
||||
|
||||
private final TransportChannel channel;
|
||||
private final TransportFilter filter;
|
||||
|
||||
SecuredTransportChannel(TransportChannel channel, TransportFilter filter) {
|
||||
this.channel = channel;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String action() {
|
||||
return channel.action();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendResponse(TransportResponse response) throws IOException {
|
||||
if (proceed(response)) {
|
||||
channel.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendResponse(TransportResponse response, TransportResponseOptions options) throws IOException {
|
||||
if (proceed(response)) {
|
||||
channel.sendResponse(response, options);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proceed(TransportResponse response) throws IOException {
|
||||
try {
|
||||
filter.outboundResponse(channel.action(), response);
|
||||
} catch (Throwable t) {
|
||||
channel.sendResponse(t);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendResponse(Throwable error) throws IOException {
|
||||
channel.sendResponse(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.transport;
|
||||
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
import org.elasticsearch.transport.TransportResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface TransportFilter {
|
||||
|
||||
/**
|
||||
* Called just before the given request is about to be sent. Any exception thrown
|
||||
* by this method will stop the request from being sent.
|
||||
*/
|
||||
void outboundRequest(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
|
||||
* be sent back to the sender.
|
||||
*/
|
||||
void inboundRequest(String action, TransportRequest request);
|
||||
|
||||
/**
|
||||
* Called just before the given response is about to be sent. Any exception thrown
|
||||
* by this method will stop the response from being sent and an error will be sent
|
||||
* instead.
|
||||
*/
|
||||
void outboundResponse(String action, TransportResponse response);
|
||||
|
||||
/**
|
||||
* Called just after the given response was received by the transport. Any exception
|
||||
* thrown by this method will stop the response from being handled normally and instead
|
||||
* the error will be used as the response.
|
||||
*/
|
||||
void inboundResponse(TransportResponse response);
|
||||
|
||||
public static class Base implements TransportFilter {
|
||||
|
||||
@Override
|
||||
public void outboundRequest(String action, TransportRequest request) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inboundRequest(String action, TransportRequest request) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outboundResponse(String action, TransportResponse response) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inboundResponse(TransportResponse response) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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.ssl.netty;
|
||||
package org.elasticsearch.shield.transport.netty;
|
||||
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.netty.channel.ChannelPipeline;
|
||||
|
@ -14,22 +14,21 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.http.netty.NettyHttpServerTransport;
|
||||
import org.elasticsearch.shield.n2n.N2NNettyUpstreamHandler;
|
||||
import org.elasticsearch.shield.ssl.SSLConfig;
|
||||
import org.elasticsearch.transport.netty.NettyTransport;
|
||||
import org.elasticsearch.shield.transport.ssl.SSLConfig;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NettySSLHttpServerTransport extends NettyHttpServerTransport {
|
||||
public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
|
||||
|
||||
private final boolean ssl;
|
||||
private final N2NNettyUpstreamHandler shieldUpstreamHandler;
|
||||
|
||||
@Inject
|
||||
public NettySSLHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays,
|
||||
N2NNettyUpstreamHandler shieldUpstreamHandler) {
|
||||
public NettySecuredHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays,
|
||||
N2NNettyUpstreamHandler shieldUpstreamHandler) {
|
||||
super(settings, networkService, bigArrays);
|
||||
this.ssl = settings.getAsBoolean("shield.http.ssl", false);
|
||||
this.shieldUpstreamHandler = shieldUpstreamHandler;
|
|
@ -3,20 +3,19 @@
|
|||
* 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.ssl.netty;
|
||||
package org.elasticsearch.shield.transport.netty;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.http.netty.NettyHttpServerTransport;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NettySSLHttpServerTransportModule extends AbstractModule {
|
||||
public class NettySecuredHttpServerTransportModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(HttpServerTransport.class).to(NettySSLHttpServerTransport.class).asEagerSingleton();
|
||||
bind(HttpServerTransport.class).to(NettySecuredHttpServerTransport.class).asEagerSingleton();
|
||||
}
|
||||
|
||||
}
|
|
@ -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.ssl.netty;
|
||||
package org.elasticsearch.shield.transport.netty;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
|
@ -14,7 +14,7 @@ import org.elasticsearch.common.network.NetworkService;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.shield.n2n.N2NNettyUpstreamHandler;
|
||||
import org.elasticsearch.shield.ssl.SSLConfig;
|
||||
import org.elasticsearch.shield.transport.ssl.SSLConfig;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.netty.NettyTransport;
|
||||
|
||||
|
@ -23,14 +23,14 @@ import javax.net.ssl.SSLEngine;
|
|||
/**
|
||||
*
|
||||
*/
|
||||
public class NettySSLTransport extends NettyTransport {
|
||||
public class NettySecuredTransport extends NettyTransport {
|
||||
|
||||
private final boolean ssl;
|
||||
private final N2NNettyUpstreamHandler shieldUpstreamHandler;
|
||||
|
||||
@Inject
|
||||
public NettySSLTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, Version version,
|
||||
N2NNettyUpstreamHandler shieldUpstreamHandler) {
|
||||
public NettySecuredTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, Version version,
|
||||
N2NNettyUpstreamHandler shieldUpstreamHandler) {
|
||||
super(settings, threadPool, networkService, bigArrays, version);
|
||||
this.shieldUpstreamHandler = shieldUpstreamHandler;
|
||||
this.ssl = settings.getAsBoolean("shield.transport.ssl", false);
|
||||
|
@ -70,7 +70,7 @@ public class NettySSLTransport extends NettyTransport {
|
|||
serverEngine.setUseClientMode(false);
|
||||
|
||||
pipeline.addFirst("ssl", new SslHandler(serverEngine));
|
||||
pipeline.replace("dispatcher", "dispatcher", new SecureMessageChannelHandler(nettyTransport, logger));
|
||||
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, logger));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public class NettySSLTransport extends NettyTransport {
|
|||
clientEngine.setUseClientMode(true);
|
||||
|
||||
pipeline.addFirst("ssl", new SslHandler(clientEngine));
|
||||
pipeline.replace("dispatcher", "dispatcher", new SecureMessageChannelHandler(nettyTransport, logger));
|
||||
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, logger));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.netty;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NettySecuredTransportModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Transport.class).to(NettySecuredTransport.class).asEagerSingleton();
|
||||
}
|
||||
}
|
|
@ -3,20 +3,20 @@
|
|||
* 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.ssl.netty;
|
||||
package org.elasticsearch.shield.transport.netty;
|
||||
|
||||
import org.elasticsearch.shield.ssl.ElasticsearchSSLException;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.netty.channel.ChannelFuture;
|
||||
import org.elasticsearch.common.netty.channel.ChannelFutureListener;
|
||||
import org.elasticsearch.common.netty.channel.ChannelHandlerContext;
|
||||
import org.elasticsearch.common.netty.channel.ChannelStateEvent;
|
||||
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
|
||||
import org.elasticsearch.shield.transport.ssl.ElasticsearchSSLException;
|
||||
import org.elasticsearch.transport.netty.MessageChannelHandler;
|
||||
|
||||
public class SecureMessageChannelHandler extends MessageChannelHandler {
|
||||
public class SecuredMessageChannelHandler extends MessageChannelHandler {
|
||||
|
||||
public SecureMessageChannelHandler(org.elasticsearch.transport.netty.NettyTransport transport, ESLogger logger) {
|
||||
public SecuredMessageChannelHandler(org.elasticsearch.transport.netty.NettyTransport transport, ESLogger logger) {
|
||||
super(transport, logger);
|
||||
}
|
||||
|
|
@ -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.ssl;
|
||||
package org.elasticsearch.shield.transport.ssl;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.rest.RestStatus;
|
|
@ -3,12 +3,11 @@
|
|||
* 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.ssl;
|
||||
package org.elasticsearch.shield.transport.ssl;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import javax.net.ssl.*;
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.support.ActionFilterChain;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.shield.authc.AuthenticationException;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.authc.system.SystemRealm;
|
||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.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;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
authcService = mock(AuthenticationService.class);
|
||||
authzService = mock(AuthorizationService.class);
|
||||
filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_WithoutDefaultToken() throws Exception {
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
filter.process("_action", request, null);
|
||||
verify(authzService).authorize(user, "_action", request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_WithDefaultToken() throws Exception {
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
AuthenticationToken defaultToken = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("_action", request, defaultToken)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
filter.process("_action", request, defaultToken);
|
||||
verify(authzService).authorize(user, "_action", request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthenticationFails_Authenticate() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.process("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthenticationFails_NoToken() throws Exception {
|
||||
thrown.expect(AuthenticationException.class);
|
||||
thrown.expectMessage("failed authc");
|
||||
TransportRequest request = new InternalRequest();
|
||||
when(authcService.token("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
|
||||
filter.process("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcess_AuthorizationFails() throws Exception {
|
||||
thrown.expect(AuthorizationException.class);
|
||||
thrown.expectMessage("failed authz");
|
||||
TransportRequest request = new InternalRequest();
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
User user = new User.Simple("_username", "r1");
|
||||
when(authcService.token("_action", request, null)).thenReturn(token);
|
||||
when(authcService.authenticate("_action", request, token)).thenReturn(user);
|
||||
doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request);
|
||||
filter.process("_action", request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransport_InboundRequest() throws Exception {
|
||||
filter = mock(SecurityFilter.class);
|
||||
SecurityFilter.Transport transport = new SecurityFilter.Transport(filter);
|
||||
InternalRequest request = new InternalRequest();
|
||||
transport.inboundRequest("_action", request);
|
||||
verify(filter).process("_action", request, SystemRealm.TOKEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransport_InboundRequest_Exception() throws Exception {
|
||||
thrown.expect(RuntimeException.class);
|
||||
thrown.expectMessage("process-error");
|
||||
filter = mock(SecurityFilter.class);
|
||||
SecurityFilter.Transport transport = new SecurityFilter.Transport(filter);
|
||||
InternalRequest request = new InternalRequest();
|
||||
doThrow(new RuntimeException("process-error")).when(filter).process("_action", request, SystemRealm.TOKEN);
|
||||
transport.inboundRequest("_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);
|
||||
action.process("_action", request, listener, chain);
|
||||
verify(filter).process("_action", request, null);
|
||||
verify(chain).continueProcessing("_action", request, listener);
|
||||
}
|
||||
|
||||
@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).process("_action", request, null);
|
||||
action.process("_action", request, listener, chain);
|
||||
verify(listener).onFailure(exception);
|
||||
verifyNoMoreInteractions(chain);
|
||||
}
|
||||
|
||||
private static class InternalRequest extends TransportRequest {
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
@Before
|
||||
public void init() throws Exception {
|
||||
token = mock(AuthenticationToken.class);
|
||||
message = mock(TransportMessage.class);
|
||||
message = new InternalMessage();
|
||||
firstRealm = mock(Realm.class);
|
||||
when(firstRealm.type()).thenReturn("first");
|
||||
secondRealm = mock(Realm.class);
|
||||
|
@ -68,6 +68,20 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
}
|
||||
verify(auditTrail).anonymousAccess("_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToken_MissingWithNullDefault() throws Exception {
|
||||
try {
|
||||
service.token("_action", message, null);
|
||||
fail("expected authentication exception with missing auth token and null default token");
|
||||
} catch (AuthenticationException ae) {
|
||||
assertThat(ae.getMessage(), equalTo("Missing authentication token for request [_action]"));
|
||||
}
|
||||
verify(auditTrail).anonymousAccess("_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -76,6 +90,21 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), notNullValue());
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), is((Object) token));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testToken_Cached() throws Exception {
|
||||
message.context().put(InternalAuthenticationService.TOKEN_CTX_KEY, token);
|
||||
AuthenticationToken result = service.token("_action", message, token);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(token));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verifyZeroInteractions(firstRealm);
|
||||
verifyZeroInteractions(secondRealm);
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), notNullValue());
|
||||
assertThat(message.context().get(InternalAuthenticationService.TOKEN_CTX_KEY), is((Object) token));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
|
@ -90,6 +119,8 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
verify(auditTrail).authenticationFailed("first", token, "_action", message);
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
|
@ -104,6 +135,25 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
|||
assertThat(result, is(user));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verify(firstRealm, never()).authenticate(token);
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthenticate_Cached() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
message.context().put(InternalAuthenticationService.USER_CTX_KEY, user);
|
||||
User result = service.authenticate("_action", message, token);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result, is(user));
|
||||
verifyZeroInteractions(auditTrail);
|
||||
verifyZeroInteractions(firstRealm);
|
||||
verifyZeroInteractions(secondRealm);
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), notNullValue());
|
||||
assertThat(message.context().get(InternalAuthenticationService.USER_CTX_KEY), is((Object) user));
|
||||
}
|
||||
|
||||
private static class InternalMessage extends TransportMessage<InternalMessage> {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.shield.authz;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -105,4 +106,9 @@ public class PrivilegeTests extends ElasticsearchTestCase {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test @LuceneTestCase.AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/22")
|
||||
public void testIndexTemplateApiIsNotPartOfClusterPrivileg() throws Exception {
|
||||
assertThat(Privilege.Cluster.ALL.predicate().apply("indices:admin/template/get"), is(false));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package org.elasticsearch.shield.n2n;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import org.elasticsearch.common.os.OsUtils;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
|
@ -15,8 +14,8 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
|||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.shield.plugin.SecurityPlugin;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLTransportModule;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransportModule;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.elasticsearch.transport.TransportModule;
|
||||
|
@ -47,9 +46,9 @@ public class IpFilteringIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
.put("node.mode", "network")
|
||||
// todo http tests fail without an explicit IP (needs investigation)
|
||||
.put("network.host", randomBoolean() ? "127.0.0.1" : "::1")
|
||||
.put("http.type", NettySSLHttpServerTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
|
||||
.put("plugin.types", SecurityPlugin.class.getName());
|
||||
.put("http.type", NettySecuredHttpServerTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransportModule.class.getName())
|
||||
.put("plugin.types", N2NPlugin.class.getName());
|
||||
//.put("shield.n2n.file", configFile.getPath())
|
||||
|
||||
if (OsUtils.MAC) {
|
||||
|
|
|
@ -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.n2n;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.plugins.AbstractPlugin;
|
||||
import org.elasticsearch.shield.n2n.N2NModule;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* a plugin that just loads the N2NModule (required for transport integration tests)
|
||||
*/
|
||||
public class N2NPlugin extends AbstractPlugin {
|
||||
@Override
|
||||
public String name() {
|
||||
return "test-n2n-plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends Module>> modules() {
|
||||
return ImmutableSet.<Class<? extends Module>>of(N2NModule.class);
|
||||
}
|
||||
}
|
|
@ -7,28 +7,64 @@ package org.elasticsearch.shield.plugin;
|
|||
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||
import org.elasticsearch.common.os.OsUtils;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.shield.transport.SecuredTransportService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
||||
import org.elasticsearch.transport.TransportModule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.headerValue;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ClusterScope(scope = Scope.SUITE, numDataNodes = 2)
|
||||
@ClusterScope(scope = Scope.SUITE, numDataNodes = 2, randomDynamicTemplates = false)
|
||||
public class ShieldPluginTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tmpFolder = new TemporaryFolder();
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return ImmutableSettings.settingsBuilder()
|
||||
File folder = newFolder();
|
||||
ImmutableSettings.Builder builder = ImmutableSettings.builder()
|
||||
.put("plugin.types", SecurityPlugin.class.getName())
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put("shield.audit.enabled", true)
|
||||
.put("shield.authc.esusers.files.users", copyFile(folder, "users"))
|
||||
.put("shield.authc.esusers.files.users_roles", copyFile(folder, "users_roles"))
|
||||
.put("shield.authz.store.files.roles", copyFile(folder, "roles.yml"))
|
||||
.put("shield.n2n.file", copyFile(folder, "ip_filter.yml"))
|
||||
.put(TransportModule.TRANSPORT_SERVICE_TYPE_KEY, SecuredTransportService.class.getName())
|
||||
// for the test internal node clients
|
||||
.put("request.headers.Authorization", headerValue("test_user", "changeme".toCharArray()));
|
||||
|
||||
if (OsUtils.MAC) {
|
||||
builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Settings transportClientSettings() {
|
||||
return ImmutableSettings.builder()
|
||||
.put("request.headers.Authorization", headerValue("test_user", "changeme".toCharArray()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -44,4 +80,25 @@ public class ShieldPluginTests extends ElasticsearchIntegrationTest {
|
|||
}
|
||||
}
|
||||
|
||||
private File newFolder() {
|
||||
try {
|
||||
return tmpFolder.newFolder();
|
||||
} catch (IOException ioe) {
|
||||
logger.error("could not create temporary folder", ioe);
|
||||
fail("could not create temporary folder");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String copyFile(File folder, String name) {
|
||||
Path file = folder.toPath().resolve(name);
|
||||
try {
|
||||
Files.copy(getClass().getResourceAsStream(name), file);
|
||||
} catch (IOException ioe) {
|
||||
logger.error("could not copy temporary configuration file [" + name + "]", ioe);
|
||||
fail("could not copy temporary configuration file [" + name + "]");
|
||||
}
|
||||
return file.toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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 com.google.common.collect.ImmutableSet;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.AbstractPlugin;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.*;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.SUITE;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ClusterScope(scope = SUITE, numDataNodes = 0)
|
||||
public class TransportFilterTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return ImmutableSettings.settingsBuilder()
|
||||
.put("plugins.load_classpath_plugins", false)
|
||||
.put("plugin.types", InternalPlugin.class.getName())
|
||||
.put(TransportModule.TRANSPORT_SERVICE_TYPE_KEY, SecuredTransportService.class.getName())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
String source = internalCluster().startNode();
|
||||
DiscoveryNode sourceNode = internalCluster().getInstance(ClusterService.class, source).localNode();
|
||||
TransportService sourceService = internalCluster().getInstance(TransportService.class, source);
|
||||
|
||||
String target = internalCluster().startNode();
|
||||
DiscoveryNode targetNode = internalCluster().getInstance(ClusterService.class, target).localNode();
|
||||
TransportService targetService = internalCluster().getInstance(TransportService.class, target);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
targetService.registerHandler("_action", new RequestHandler(new Response("trgt_to_src"), latch));
|
||||
sourceService.sendRequest(targetNode, "_action", new Request("src_to_trgt"), new ResponseHandler(new Response("trgt_to_src"), latch));
|
||||
await(latch);
|
||||
|
||||
latch = new CountDownLatch(2);
|
||||
sourceService.registerHandler("_action", new RequestHandler(new Response("src_to_trgt"), latch));
|
||||
targetService.sendRequest(sourceNode, "_action", new Request("trgt_to_src"), new ResponseHandler(new Response("src_to_trgt"), latch));
|
||||
await(latch);
|
||||
|
||||
|
||||
TransportFilter sourceFilter = internalCluster().getInstance(TransportFilter.class, source);
|
||||
TransportFilter targetFilter = internalCluster().getInstance(TransportFilter.class, target);
|
||||
InOrder inOrder = inOrder(sourceFilter, targetFilter);
|
||||
inOrder.verify(sourceFilter).outboundRequest("_action", new Request("src_to_trgt"));
|
||||
inOrder.verify(targetFilter).inboundRequest("_action", new Request("src_to_trgt"));
|
||||
inOrder.verify(targetFilter).outboundResponse("_action", new Response("trgt_to_src"));
|
||||
inOrder.verify(sourceFilter).inboundResponse(new Response("trgt_to_src"));
|
||||
inOrder.verify(targetFilter).outboundRequest("_action", new Request("trgt_to_src"));
|
||||
inOrder.verify(sourceFilter).inboundRequest("_action", new Request("trgt_to_src"));
|
||||
inOrder.verify(sourceFilter).outboundResponse("_action", new Response("src_to_trgt"));
|
||||
inOrder.verify(targetFilter).inboundResponse(new Response("src_to_trgt"));
|
||||
}
|
||||
|
||||
public static class InternalPlugin extends AbstractPlugin {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "test-transport-filter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends Module>> modules() {
|
||||
return ImmutableSet.<Class<? extends Module>>of(TestTransportFilterModule.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestTransportFilterModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(TransportFilter.class).toInstance(mock(TransportFilter.class));
|
||||
}
|
||||
}
|
||||
|
||||
static class Request extends TransportRequest {
|
||||
|
||||
private String msg;
|
||||
|
||||
Request() {
|
||||
}
|
||||
|
||||
Request(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
msg = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Request request = (Request) o;
|
||||
|
||||
if (!msg.equals(request.msg)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return msg.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class Response extends TransportResponse {
|
||||
|
||||
private String msg;
|
||||
|
||||
Response() {
|
||||
}
|
||||
|
||||
Response(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
msg = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Response response = (Response) o;
|
||||
|
||||
if (!msg.equals(response.msg)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return msg.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class RequestHandler implements TransportRequestHandler<Request> {
|
||||
|
||||
private final Response response;
|
||||
private final CountDownLatch latch;
|
||||
|
||||
RequestHandler(Response response, CountDownLatch latch) {
|
||||
this.response = response;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request newInstance() {
|
||||
return new Request();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(Request request, TransportChannel channel) throws Exception {
|
||||
channel.sendResponse(response);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executor() {
|
||||
return ThreadPool.Names.SAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceExecution() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseHandler implements TransportResponseHandler<Response> {
|
||||
|
||||
private final Response response;
|
||||
private final CountDownLatch latch;
|
||||
|
||||
ResponseHandler(Response response, CountDownLatch latch) {
|
||||
this.response = response;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response newInstance() {
|
||||
return new Response();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Response response) {
|
||||
assertThat(response, equalTo(this.response));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(TransportException exp) {
|
||||
logger.error("execution of request failed", exp);
|
||||
fail("execution of request failed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executor() {
|
||||
return ThreadPool.Names.SAME;
|
||||
}
|
||||
}
|
||||
|
||||
static void await(CountDownLatch latch) throws Exception {
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for request");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.ssl;
|
||||
package org.elasticsearch.shield.transport.ssl;
|
||||
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
@ -25,7 +25,7 @@ public class SSLConfigTests extends ElasticsearchTestCase {
|
|||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
testnodeStore = new File(getClass().getResource("/certs/simple/testnode.jks").toURI());
|
||||
testnodeStore = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());
|
||||
}
|
||||
|
||||
@Test
|
|
@ -3,12 +3,11 @@
|
|||
* 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.ssl;
|
||||
package org.elasticsearch.shield.transport.ssl;
|
||||
|
||||
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
|
||||
import org.elasticsearch.client.Client;
|
||||
|
@ -22,19 +21,20 @@ import org.elasticsearch.common.transport.TransportAddress;
|
|||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.node.NodeBuilder;
|
||||
import org.elasticsearch.shield.plugin.SecurityPlugin;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLTransportModule;
|
||||
import org.elasticsearch.shield.n2n.N2NPlugin;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransportModule;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.elasticsearch.transport.TransportModule;
|
||||
import org.junit.*;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
@ -63,7 +63,7 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
File testnodeStore;
|
||||
try {
|
||||
testnodeStore = new File(getClass().getResource("/certs/simple/testnode.jks").toURI());
|
||||
testnodeStore = new File(getClass().getResource("certs/simple/testnode.jks").toURI());
|
||||
assertThat(testnodeStore.exists(), is(true));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -88,9 +88,10 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
.put("shield.http.ssl.truststore", testnodeStore.getPath())
|
||||
.put("shield.http.ssl.truststore_password", "testnode")
|
||||
// SSL SETUP
|
||||
.put("http.type", NettySSLHttpServerTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
|
||||
.put("plugin.types", SecurityPlugin.class.getName())
|
||||
.put("http.type", NettySecuredHttpServerTransportModule.class.getName())
|
||||
.put("plugins.load_classpath_plugins", false)
|
||||
.put("plugin.types", N2NPlugin.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransportModule.class.getName())
|
||||
.put("shield.n2n.file", ipFilterFile.getPath());
|
||||
|
||||
if (OsUtils.MAC) {
|
||||
|
@ -210,8 +211,8 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
File testClientKeyStore;
|
||||
File testClientTrustStore;
|
||||
try {
|
||||
testClientKeyStore = new File(getClass().getResource("/certs/simple/testclient.jks").toURI());
|
||||
testClientTrustStore = new File(getClass().getResource("/certs/simple/testclient.jks").toURI());
|
||||
testClientKeyStore = new File(getClass().getResource("certs/simple/testclient.jks").toURI());
|
||||
testClientTrustStore = new File(getClass().getResource("certs/simple/testclient.jks").toURI());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -220,15 +221,15 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
|
||||
return ImmutableSettings.settingsBuilder()
|
||||
.put("node.name", name)
|
||||
.put("plugins.load_classpath_plugins", false)
|
||||
.put("shield.transport.ssl", true)
|
||||
.put("shield.transport.ssl.keystore", testClientKeyStore.getPath())
|
||||
.put("shield.transport.ssl.keystore_password", "testclient")
|
||||
.put("shield.transport.ssl.truststore", testClientTrustStore .getPath())
|
||||
.put("shield.transport.ssl.truststore_password", "testclient")
|
||||
.put("discovery.zen.ping.multicast.ping.enabled", false)
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransportModule.class.getName())
|
||||
.put("shield.n2n.file", ipFilterFile.getPath())
|
||||
//.put("plugin.types", SecurityPlugin.class.getName())
|
||||
.put("cluster.name", internalCluster().getClusterName());
|
||||
}
|
||||
|
||||
|
@ -237,4 +238,5 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest {
|
|||
assertNoTimeout(clusterHealthResponse);
|
||||
assertThat(clusterHealthResponse.getStatus(), is(ClusterHealthStatus.GREEN));
|
||||
}
|
||||
|
||||
}
|
|
@ -3,12 +3,11 @@
|
|||
* 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.ssl;
|
||||
package org.elasticsearch.shield.transport.ssl;
|
||||
|
||||
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.os.OsUtils;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
|
@ -16,9 +15,9 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.shield.plugin.SecurityPlugin;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.ssl.netty.NettySSLTransportModule;
|
||||
import org.elasticsearch.shield.n2n.N2NPlugin;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredHttpServerTransportModule;
|
||||
import org.elasticsearch.shield.transport.netty.NettySecuredTransportModule;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
||||
import org.elasticsearch.transport.TransportModule;
|
||||
|
@ -30,7 +29,6 @@ import org.junit.rules.TemporaryFolder;
|
|||
import javax.net.ssl.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
@ -68,7 +66,7 @@ public class SslRequireAuthTests extends ElasticsearchIntegrationTest {
|
|||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
File testnodeStore;
|
||||
try {
|
||||
testnodeStore = new File(getClass().getResource("/certs/simple/testnode.jks").toURI());
|
||||
testnodeStore = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());
|
||||
assertThat(testnodeStore.exists(), is(true));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -94,9 +92,10 @@ public class SslRequireAuthTests extends ElasticsearchIntegrationTest {
|
|||
.put("shield.http.ssl.truststore", testnodeStore.getPath())
|
||||
.put("shield.http.ssl.truststore_password", "testnode")
|
||||
// SSL SETUP
|
||||
.put("http.type", NettySSLHttpServerTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
|
||||
.put("plugin.types", SecurityPlugin.class.getName())
|
||||
.put("http.type", NettySecuredHttpServerTransportModule.class.getName())
|
||||
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransportModule.class.getName())
|
||||
.put("plugins.load_classpath_plugins", false)
|
||||
.put("plugin.types", N2NPlugin.class.getName())
|
||||
.put("shield.n2n.file", ipFilterFile.getPath());
|
||||
|
||||
if (OsUtils.MAC) {
|
||||
|
@ -139,7 +138,7 @@ public class SslRequireAuthTests extends ElasticsearchIntegrationTest {
|
|||
@Test
|
||||
@TestLogging("_root:DEBUG")
|
||||
public void testThatConnectionToHTTPWorks() throws Exception {
|
||||
File store = new File(getClass().getResource("/certs/simple/testnode.jks").toURI());
|
||||
File store = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());
|
||||
|
||||
KeyStore ks;
|
||||
KeyManagerFactory kmf;
|
|
@ -7,3 +7,9 @@ log4j.additivity.org.apache.http=false
|
|||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
||||
|
||||
log4j.logger.shield.audit.logfile=TRACE, access_log
|
||||
|
||||
log4j.appender.access_log=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.access_log.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.access_log.layout.conversionPattern=%m%n
|
|
@ -0,0 +1,2 @@
|
|||
allow: all
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
user:
|
||||
cluster: ALL
|
||||
indices:
|
||||
'.*': ALL
|
|
@ -0,0 +1 @@
|
|||
test_user:{plain}changeme
|
|
@ -0,0 +1 @@
|
|||
test_user:user
|
Loading…
Reference in New Issue