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:
uboness 2014-08-13 15:12:05 +02:00
parent 452367b674
commit 9c55be1530
49 changed files with 1163 additions and 136 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.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;

View File

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

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.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;
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.ssl;
package org.elasticsearch.shield.transport.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.rest.RestStatus;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.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);
}
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
allow: all

View File

@ -0,0 +1,4 @@
user:
cluster: ALL
indices:
'.*': ALL

View File

@ -0,0 +1 @@
test_user:{plain}changeme

View File

@ -0,0 +1 @@
test_user:user