Merge branch 'master' into doc-draft

Merging PR elastic/elasticsearch#99 per Uri

Original commit: elastic/x-pack-elasticsearch@022a898a9f
This commit is contained in:
Paul Echeverri 2014-10-01 12:59:20 -07:00
commit a0a7b9b7ff
90 changed files with 2385 additions and 518 deletions

20
pom.xml
View File

@ -30,8 +30,9 @@
</repositories>
<properties>
<lucene.version>4.10.0</lucene.version>
<elasticsearch.version>1.4.0-SNAPSHOT</elasticsearch.version>
<lucene.version>4.10.1</lucene.version>
<lucene.maven.version>4.10.1</lucene.maven.version>
<elasticsearch.version>1.4.0.Beta1-SNAPSHOT</elasticsearch.version>
<tests.jvms>auto</tests.jvms>
<tests.shuffle>true</tests.shuffle>
@ -42,6 +43,7 @@
<tests.heap.size>512m</tests.heap.size>
<tests.topn>5</tests.topn>
<execution.hint.file>.local-${project.version}-execution-hints.log</execution.hint.file>
<tests.rest>false</tests.rest>
</properties>
<dependencies>
@ -49,7 +51,7 @@
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-test-framework</artifactId>
<version>${lucene.version}</version>
<version>${lucene.maven.version}</version>
<scope>test</scope>
</dependency>
<dependency>
@ -119,6 +121,18 @@
<version>2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.3.2</version>
<scope>test</scope>
</dependency>
<!-- real dependencies -->

View File

@ -8,20 +8,30 @@ package org.elasticsearch.shield;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.*;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.system.SystemRealm;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.authz.SystemRole;
import org.elasticsearch.shield.key.KeyService;
import org.elasticsearch.shield.key.SignatureException;
import org.elasticsearch.shield.transport.TransportFilter;
import org.elasticsearch.transport.TransportRequest;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
@ -29,15 +39,19 @@ public class SecurityFilter extends AbstractComponent {
private final AuthenticationService authcService;
private final AuthorizationService authzService;
private final KeyService keyService;
private final AuditTrail auditTrail;
@Inject
public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService) {
public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, KeyService keyService, AuditTrail auditTrail) {
super(settings);
this.authcService = authcService;
this.authzService = authzService;
this.keyService = keyService;
this.auditTrail = auditTrail;
}
void process(String action, TransportRequest request) {
User authenticateAndAuthorize(String action, TransportRequest request) {
// if the action is a system action, we'll fall back on the system user, otherwise we
// won't fallback on any user and an authentication exception will be thrown
@ -46,6 +60,56 @@ public class SecurityFilter extends AbstractComponent {
AuthenticationToken token = authcService.token(action, request, defaultToken);
User user = authcService.authenticate(action, request, token);
authzService.authorize(user, action, request);
return user;
}
User authenticate(RestRequest request) {
AuthenticationToken token = authcService.token(request);
return authcService.authenticate(request, token);
}
<Request extends ActionRequest> Request unsign(User user, String action, Request request) {
try {
if (request instanceof SearchScrollRequest) {
SearchScrollRequest scrollRequest = (SearchScrollRequest) request;
String scrollId = scrollRequest.scrollId();
scrollRequest.scrollId(keyService.unsignAndVerify(scrollId));
return request;
}
if (request instanceof ClearScrollRequest) {
ClearScrollRequest clearScrollRequest = (ClearScrollRequest) request;
List<String> signedIds = clearScrollRequest.scrollIds();
List<String> unsignedIds = new ArrayList<>(signedIds.size());
for (String signedId : signedIds) {
unsignedIds.add(keyService.unsignAndVerify(signedId));
}
clearScrollRequest.scrollIds(unsignedIds);
return request;
}
return request;
} catch (SignatureException se) {
auditTrail.tamperedRequest(user, action, request);
throw new AuthorizationException("Invalid request: " + se.getMessage());
}
}
<Response extends ActionResponse> Response sign(User user, String action, Response response) {
if (response instanceof SearchResponse) {
SearchResponse searchResponse = (SearchResponse) response;
String scrollId = searchResponse.getScrollId();
if (scrollId != null && !keyService.signed(scrollId)) {
searchResponse.scrollId(keyService.sign(scrollId));
}
return response;
}
return response;
}
public static class Rest extends RestFilter {
@ -65,7 +129,7 @@ public class SecurityFilter extends AbstractComponent {
@Override
public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception {
filter.authcService.extractAndRegisterToken(request);
filter.authenticate(request);
filterChain.continueProcessing(request, channel);
}
}
@ -81,7 +145,7 @@ public class SecurityFilter extends AbstractComponent {
@Override
public void inboundRequest(String action, TransportRequest request) {
filter.process(action, request);
filter.authenticateAndAuthorize(action, request);
}
}
@ -97,8 +161,9 @@ public class SecurityFilter extends AbstractComponent {
@Override
public void apply(String action, ActionRequest request, ActionListener listener, ActionFilterChain chain) {
try {
filter.process(action, request);
chain.proceed(action, request, listener);
User user = filter.authenticateAndAuthorize(action, request);
request = filter.unsign(user, action, request);
chain.proceed(action, request, new SigningListener(user, action, filter, listener));
} catch (Throwable t) {
listener.onFailure(t);
}
@ -114,4 +179,30 @@ public class SecurityFilter extends AbstractComponent {
return Integer.MIN_VALUE;
}
}
static class SigningListener<Response extends ActionResponse> implements ActionListener<Response> {
private final User user;
private final String action;
private final SecurityFilter filter;
private final ActionListener innerListener;
private SigningListener(User user, String action, SecurityFilter filter, ActionListener innerListener) {
this.user = user;
this.action = action;
this.filter = filter;
this.innerListener = innerListener;
}
@Override
public void onResponse(Response response) {
response = this.filter.sign(user, action, response);
innerListener.onResponse(response);
}
@Override
public void onFailure(Throwable e) {
innerListener.onFailure(e);
}
}
}

View File

@ -5,15 +5,20 @@
*/
package org.elasticsearch.shield;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class SecurityFilterModule extends AbstractModule {
public class SecurityFilterModule extends AbstractShieldModule.Node {
public SecurityFilterModule(Settings settings) {
super(settings);
}
@Override
protected void configure() {
protected void configureNode() {
bind(SecurityFilter.class).asEagerSingleton();
}
}

View File

@ -9,24 +9,24 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import java.util.List;
/**
*
*/
public class SecurityException extends ElasticsearchException.WithRestHeaders {
public class ShieldException 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 +"\""))
.put("WWW-Authenticate", Lists.newArrayList("Basic realm=\""+ ShieldPlugin.NAME +"\""))
.build();
public SecurityException(String msg) {
public ShieldException(String msg) {
super(msg, HEADERS);
}
public SecurityException(String msg, Throwable cause) {
public ShieldException(String msg, Throwable cause) {
super(msg, cause, HEADERS);
}
}

View File

@ -7,61 +7,60 @@ 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.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.key.KeyModule;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.shield.transport.SecuredRestModule;
import org.elasticsearch.shield.transport.SecuredTransportModule;
/**
*
*/
public class SecurityModule extends AbstractModule implements SpawnModules, PreProcessModule {
public class ShieldModule extends AbstractShieldModule.Spawn implements PreProcessModule {
private final Settings settings;
private final boolean isClient;
private final boolean isShieldEnabled;
private final boolean enabled;
public SecurityModule(Settings settings) {
this.settings = settings;
this.isClient = settings.getAsBoolean("node.client", false);
this.isShieldEnabled = settings.getAsBoolean("shield.enabled", true);
public ShieldModule(Settings settings) {
super(settings);
this.enabled = settings.getAsBoolean("shield.enabled", true);
}
@Override
public void processModule(Module module) {
if (module instanceof ActionModule && isShieldEnabled && !isClient) {
if (module instanceof ActionModule && enabled && !clientMode) {
((ActionModule) module).registerFilter(SecurityFilter.Action.class);
}
}
@Override
public Iterable<? extends Module> spawnModules() {
public Iterable<? extends Module> spawnModules(boolean clientMode) {
// don't spawn modules if shield is explicitly disabled
if (!isShieldEnabled) {
if (!enabled) {
return ImmutableList.of();
}
// spawn needed parts in client mode
if (isClient) {
return ImmutableList.of(new SecuredTransportModule(), new SecurityFilterModule());
if (clientMode) {
return ImmutableList.of(new SecuredTransportModule(settings));
}
return ImmutableList.of(
new AuthenticationModule(settings),
new AuthorizationModule(),
new AuthorizationModule(settings),
new AuditTrailModule(settings),
new SecuredTransportModule(),
new SecuredRestModule(),
new SecurityFilterModule());
new SecuredTransportModule(settings),
new SecuredRestModule(settings),
new SecurityFilterModule(settings),
new KeyModule(settings));
}
@Override
protected void configure() {
protected void configure(boolean clientMode) {
}
}

View File

@ -8,13 +8,13 @@ package org.elasticsearch.shield;
/**
*
*/
public class SecuritySettingsException extends SecurityException {
public class ShieldSettingsException extends ShieldException {
public SecuritySettingsException(String msg) {
public ShieldSettingsException(String msg) {
super(msg);
}
public SecuritySettingsException(String msg, Throwable cause) {
public ShieldSettingsException(String msg, Throwable cause) {
super(msg, cause);
}

View File

@ -24,7 +24,7 @@ public class ShieldVersion implements Serializable {
// the (internal) format of the id is there so we can easily do after/before checks on the id
public static final int V_1_0_0_ID = /*00*/1000099;
public static final ShieldVersion V_1_0_0 = new ShieldVersion(V_1_0_0_ID, true, Version.V_1_4_0);
public static final ShieldVersion V_1_0_0 = new ShieldVersion(V_1_0_0_ID, true, Version.V_1_4_0_Beta1);
public static final ShieldVersion CURRENT = V_1_0_0;

View File

@ -5,10 +5,12 @@
*/
package org.elasticsearch.shield.audit;
import org.elasticsearch.shield.ShieldException;
/**
*
*/
public class AuditException extends org.elasticsearch.shield.SecurityException {
public class AuditException extends ShieldException {
public AuditException(String msg) {
super(msg);

View File

@ -5,9 +5,11 @@
*/
package org.elasticsearch.shield.audit;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
/**
*
@ -31,10 +33,18 @@ public interface AuditTrail {
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
}
@Override
public void authenticationFailed(AuthenticationToken token, RestRequest request) {
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message) {
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) {
}
@Override
public void accessGranted(User user, String action, TransportMessage<?> message) {
}
@ -42,6 +52,10 @@ public interface AuditTrail {
@Override
public void accessDenied(User user, String action, TransportMessage<?> message) {
}
@Override
public void tamperedRequest(User user, String action, TransportRequest request) {
}
};
String name();
@ -50,10 +64,16 @@ public interface AuditTrail {
void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message);
void authenticationFailed(AuthenticationToken token, RestRequest request);
void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message);
void authenticationFailed(String realm, AuthenticationToken token, RestRequest request);
void accessGranted(User user, String action, TransportMessage<?> message);
void accessDenied(User user, String action, TransportMessage<?> message);
void tamperedRequest(User user, String action, TransportRequest request);
}

View File

@ -7,27 +7,28 @@ package org.elasticsearch.shield.audit;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.multibindings.Multibinder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.audit.logfile.LoggingAuditTrail;
import org.elasticsearch.shield.support.AbstractShieldModule;
import java.util.Set;
/**
*
*/
public class AuditTrailModule extends AbstractModule {
public class AuditTrailModule extends AbstractShieldModule.Node {
private final Settings settings;
private final boolean enabled;
public AuditTrailModule(Settings settings) {
this.settings = settings;
super(settings);
enabled = settings.getAsBoolean("shield.audit.enabled", false);
}
@Override
protected void configure() {
if (!settings.getAsBoolean("shield.audit.enabled", false)) {
protected void configureNode() {
if (!enabled) {
bind(AuditTrail.class).toInstance(AuditTrail.NOOP);
return;
}

View File

@ -8,9 +8,11 @@ package org.elasticsearch.shield.audit;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
import java.util.Set;
@ -34,37 +36,57 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
@Override
public void anonymousAccess(String action, TransportMessage<?> message) {
for (int i = 0; i < auditTrails.length; i++) {
auditTrails[i].anonymousAccess(action, message);
for (AuditTrail auditTrail : auditTrails) {
auditTrail.anonymousAccess(action, message);
}
}
@Override
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
for (int i = 0; i < auditTrails.length; i++) {
auditTrails[i].authenticationFailed(token, action, message);
for (AuditTrail auditTrail : auditTrails) {
auditTrail.authenticationFailed(token, action, message);
}
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message) {
for (int i = 0; i < auditTrails.length; i++) {
auditTrails[i].authenticationFailed(realm, token, action, message);
for (AuditTrail auditTrail : auditTrails) {
auditTrail.authenticationFailed(realm, token, action, message);
}
}
@Override
public void authenticationFailed(AuthenticationToken token, RestRequest request) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.authenticationFailed(token, request);
}
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.authenticationFailed(realm, token, request);
}
}
@Override
public void accessGranted(User user, String action, TransportMessage<?> message) {
for (int i = 0; i < auditTrails.length; i++) {
auditTrails[i].accessGranted(user, action, message);
for (AuditTrail auditTrail : auditTrails) {
auditTrail.accessGranted(user, action, message);
}
}
@Override
public void accessDenied(User user, String action, TransportMessage<?> message) {
for (int i = 0; i < auditTrails.length; i++) {
auditTrails[i].accessDenied(user, action, message);
for (AuditTrail auditTrail : auditTrails) {
auditTrail.accessDenied(user, action, message);
}
}
@Override
public void tamperedRequest(User user, String action, TransportRequest request) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.tamperedRequest(user, action, request);
}
}
}

View File

@ -9,10 +9,12 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
/**
*
@ -55,6 +57,15 @@ public class LoggingAuditTrail implements AuditTrail {
}
}
@Override
public void authenticationFailed(AuthenticationToken token, RestRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("AUTHENTICATION_FAILED\thost=[{}], URI=[{}], principal=[{}], request=[{}]", request.getRemoteAddress(), request.uri(), token.principal(), request);
} else {
logger.error("AUTHENTICATION_FAILED\thost=[{}], URI=[{}], principal=[{}]", request.getRemoteAddress(), request.uri(), token.principal());
}
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message) {
if (logger.isTraceEnabled()) {
@ -62,6 +73,13 @@ public class LoggingAuditTrail implements AuditTrail {
}
}
@Override
public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) {
if (logger.isTraceEnabled()) {
logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], URI=[{}], principal=[{}], request=[{}]", realm, request.getRemoteAddress(), request.uri(), token.principal(), request);
}
}
@Override
public void accessGranted(User user, String action, TransportMessage<?> message) {
if (logger.isDebugEnabled()) {
@ -80,4 +98,12 @@ public class LoggingAuditTrail implements AuditTrail {
}
}
@Override
public void tamperedRequest(User user, String action, TransportRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("TAMPERED REQUEST\thost=[{}], action=[{}], principal=[{}], request=[{}]", request.remoteAddress(), action, user.principal(), request);
} else {
logger.error("TAMPERED REQUEST\thost=[{}], action=[{}], principal=[{}]", request.remoteAddress(), action, user.principal());
}
}
}

View File

@ -6,11 +6,12 @@
package org.elasticsearch.shield.authc;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.ShieldException;
/**
*
*/
public class AuthenticationException extends org.elasticsearch.shield.SecurityException {
public class AuthenticationException extends ShieldException {
public AuthenticationException(String msg) {
super(msg);

View File

@ -6,55 +6,31 @@
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.SpawnModules;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.esusers.ESUsersModule;
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.ldap.LdapModule;
import org.elasticsearch.shield.authc.ldap.LdapRealm;
import org.elasticsearch.shield.authc.system.SystemRealm;
import static org.elasticsearch.common.inject.name.Names.named;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class AuthenticationModule extends AbstractModule implements SpawnModules {
private final Settings settings;
private final boolean esusersEnabled;
private final boolean ldapEnabled;
public class AuthenticationModule extends AbstractShieldModule.Node.Spawn {
public AuthenticationModule(Settings settings) {
this.settings = settings;
this.esusersEnabled = ESUsersModule.enabled(settings);
this.ldapEnabled = LdapModule.enabled(settings);
super(settings);
}
@Override
public Iterable<? extends Module> spawnModules() {
ImmutableList.Builder<Module> modules = ImmutableList.builder();
modules.add(new SystemRealm.Module());
if (esusersEnabled) {
modules.add(new ESUsersModule());
}
if (ldapEnabled) {
modules.add(new LdapModule(settings));
}
return modules.build();
public Iterable<? extends Node> spawnModules() {
return ImmutableList.of(
new SystemRealm.Module(settings),
new ESUsersModule(settings),
new LdapModule(settings));
}
@Override
protected void configure() {
protected void configureNode() {
bind(AuthenticationService.class).to(InternalAuthenticationService.class).asEagerSingleton();
if (!esusersEnabled) {
bind(ESUsersRealm.class).toProvider(Providers.of((ESUsersRealm) null));
}
if (!ldapEnabled) {
bind(LdapRealm.class).toProvider(Providers.of((LdapRealm) null));
}
}
}

View File

@ -18,7 +18,7 @@ public interface AuthenticationService {
* Extracts an authentication token from the given rest request and if found registers it on
* the request. If not found, an {@link AuthenticationException} is thrown.
*/
void extractAndRegisterToken(RestRequest request) throws AuthenticationException;
AuthenticationToken token(RestRequest request) throws AuthenticationException;
/**
* Extracts the authenticate token from the given message. If no recognized auth token is associated
@ -49,4 +49,18 @@ public interface AuthenticationService {
*/
User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException;
/**
* Authenticates the user associated with the given request based on the given authentication token.
*
* On successful authentication, the {@link org.elasticsearch.shield.User user} that is associated
* with the request (i.e. that is associated with the token's {@link AuthenticationToken#principal() principal})
* will be returned. If authentication fails, an {@link AuthenticationException} will be thrown.
*
* @param request The executed request
* @param token The authentication token associated with the given request (must not be {@code null})
* @return The authenticated User
* @throws AuthenticationException If no user could be authenticated (can either be due to missing
* supported authentication token, or simply due to bad credentials.
*/
User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException;
}

View File

@ -13,4 +13,6 @@ public interface AuthenticationToken {
String principal();
Object credentials();
void clearCredentials();
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.shield.authc;
import org.elasticsearch.common.ContextHolder;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
@ -35,12 +36,12 @@ public class InternalAuthenticationService extends AbstractComponent implements
}
@Override
public void extractAndRegisterToken(RestRequest request) throws AuthenticationException {
public AuthenticationToken token(RestRequest request) throws AuthenticationException {
for (Realm realm : realms) {
AuthenticationToken token = realm.token(request);
if (token != null) {
request.putInContext(TOKEN_CTX_KEY, token);
return;
return token;
}
}
throw new AuthenticationException("Missing authentication token");
@ -97,24 +98,52 @@ 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.getContext().get(USER_CTX_KEY);
if (user != null) {
return user;
}
for (Realm realm : realms) {
if (realm.supports(token)) {
user = realm.authenticate(token);
if (user != null) {
message.putInContext(USER_CTX_KEY, user);
return user;
} else if (auditTrail != null) {
auditTrail.authenticationFailed(realm.type(), token, action, message);
try {
User user = (User) message.getContext().get(USER_CTX_KEY);
if (user != null) {
return user;
}
for (Realm realm : realms) {
if (realm.supports(token)) {
user = realm.authenticate(token);
if (user != null) {
message.putInContext(USER_CTX_KEY, user);
return user;
} else if (auditTrail != null) {
auditTrail.authenticationFailed(realm.type(), token, action, message);
}
}
}
if (auditTrail != null) {
auditTrail.authenticationFailed(token, action, message);
}
throw new AuthenticationException("Unable to authenticate user for request");
} finally {
token.clearCredentials();
}
if (auditTrail != null) {
auditTrail.authenticationFailed(token, action, message);
}
@Override
public User authenticate(RestRequest request, AuthenticationToken token) throws AuthenticationException {
assert token != null : "cannot authenticate null tokens";
try {
for (Realm realm : realms) {
if (realm.supports(token)) {
User user = realm.authenticate(token);
if (user != null) {
request.putInContext(USER_CTX_KEY, user);
return user;
} else if (auditTrail != null) {
auditTrail.authenticationFailed(realm.type(), token, request);
}
}
}
if (auditTrail != null) {
auditTrail.authenticationFailed(token, request);
}
throw new AuthenticationException("Unable to authenticate user for request");
} finally {
token.clearCredentials();
}
throw new AuthenticationException("Unable to authenticate user for request");
}
}

View File

@ -5,27 +5,39 @@
*/
package org.elasticsearch.shield.authc.esusers;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.support.UserPasswdStore;
import org.elasticsearch.shield.authc.support.UserRolesStore;
import org.elasticsearch.shield.support.AbstractShieldModule;
import static org.elasticsearch.common.inject.name.Names.named;
/**
*
*/
public class ESUsersModule extends AbstractModule {
public class ESUsersModule extends AbstractShieldModule.Node {
public static boolean enabled(Settings settings) {
return settings.getComponentSettings(ESUsersModule.class).getAsBoolean("enabled", true);
private final boolean enabled;
public ESUsersModule(Settings settings) {
super(settings);
enabled = enabled(settings);
}
@Override
protected void configure() {
bind(Realm.class).annotatedWith(named(ESUsersRealm.TYPE)).to(ESUsersRealm.class).asEagerSingleton();
bind(UserPasswdStore.class).annotatedWith(named("file")).to(FileUserPasswdStore.class).asEagerSingleton();
bind(UserRolesStore.class).annotatedWith(named("file")).to(FileUserRolesStore.class).asEagerSingleton();
protected void configureNode() {
if (enabled) {
bind(Realm.class).annotatedWith(named(ESUsersRealm.TYPE)).to(ESUsersRealm.class).asEagerSingleton();
bind(UserPasswdStore.class).annotatedWith(named("file")).to(FileUserPasswdStore.class).asEagerSingleton();
bind(UserRolesStore.class).annotatedWith(named("file")).to(FileUserRolesStore.class).asEagerSingleton();
} else {
bind(ESUsersRealm.class).toProvider(Providers.<ESUsersRealm>of(null));
}
}
static boolean enabled(Settings settings) {
return settings.getComponentSettings(ESUsersModule.class).getAsBoolean("enabled", true);
}
}

View File

@ -10,7 +10,7 @@ import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.name.Named;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
@ -25,20 +25,18 @@ import org.elasticsearch.transport.TransportMessage;
*/
public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePasswordToken> {
static {
BaseRestHandler.addUsefulHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
public static final String TYPE = "esusers";
final UserPasswdStore userPasswdStore;
final UserRolesStore userRolesStore;
@Inject
public ESUsersRealm(Settings settings, @Named("file") UserPasswdStore userPasswdStore, @Named("file") UserRolesStore userRolesStore) {
public ESUsersRealm(Settings settings, @Named("file") UserPasswdStore userPasswdStore,
@Named("file") UserRolesStore userRolesStore, RestController restController) {
super(settings);
this.userPasswdStore = userPasswdStore;
this.userRolesStore = userRolesStore;
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
@Override

View File

@ -15,8 +15,9 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UserPasswdStore;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -61,7 +62,7 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
}
@Override
public boolean verifyPassword(String username, char[] password) {
public boolean verifyPassword(String username, SecuredString password) {
if (esUsers == null) {
return false;
}
@ -75,8 +76,7 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get("shield.authc.esusers.files.users");
if (location == null) {
File shieldDirectory = new File(env.configFile(), SecurityPlugin.NAME);
return shieldDirectory.toPath().resolve(".users");
return ShieldPlugin.resolveConfigFile(env, ".users");
}
return Paths.get(location);
}
@ -86,6 +86,9 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
* empty map is returned
*/
public static ImmutableMap<String, char[]> parseFile(Path path, @Nullable ESLogger logger) {
if (logger != null) {
logger.trace("Reading users file located at [{}]", path);
}
if (!Files.exists(path)) {
return ImmutableMap.of();
}

View File

@ -16,7 +16,7 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.support.UserRolesStore;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -68,8 +68,7 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get("shield.authc.esusers.files.users_roles");
if (location == null) {
File shieldDirectory = new File(env.configFile(), SecurityPlugin.NAME);
return shieldDirectory.toPath().resolve(".users_roles");
return ShieldPlugin.resolveConfigFile(env, ".users_roles");
}
return Paths.get(location);
}
@ -79,6 +78,10 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
* an empty map is returned
*/
public static ImmutableMap<String, String[]> parseFile(Path path, @Nullable ESLogger logger) {
if (logger != null) {
logger.trace("Reading users roles file located at [{}]", path);
}
if (!Files.exists(path)) {
return ImmutableMap.of();
}

View File

@ -11,12 +11,16 @@ import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.collect.*;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.collect.ObjectArrays;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import java.nio.file.Files;
import java.nio.file.Path;
@ -97,14 +101,14 @@ public class ESUsersTool extends CliTool {
String rolesCsv = cli.getOptionValue("roles");
String[] roles = (rolesCsv != null) ? rolesCsv.split(",") : Strings.EMPTY_ARRAY;
return new Useradd(terminal, username, password, roles);
return new Useradd(terminal, username, new SecuredString(password), roles);
}
final String username;
final char[] passwd;
final SecuredString passwd;
final String[] roles;
Useradd(Terminal terminal, String username, char[] passwd, String... roles) {
Useradd(Terminal terminal, String username, SecuredString passwd, String... roles) {
super(terminal);
this.username = username;
this.passwd = passwd;
@ -212,12 +216,13 @@ public class ESUsersTool extends CliTool {
}
final String username;
final char[] passwd;
final SecuredString passwd;
Passwd(Terminal terminal, String username, char[] passwd) {
super(terminal);
this.username = username;
this.passwd = passwd;
this.passwd = new SecuredString(passwd);
Arrays.fill(passwd, (char) 0);
}
@Override

View File

@ -10,6 +10,8 @@ import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.authc.support.SecuredString;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
@ -42,7 +44,7 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
super(settings);
domainName = componentSettings.get(AD_DOMAIN_NAME_SETTING);
if (domainName == null) {
throw new org.elasticsearch.shield.SecurityException("Missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory");
throw new ShieldException("Missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory");
}
userSearchDN = componentSettings.get(AD_USER_SEARCH_BASEDN_SETTING, buildDnFromDomain(domainName));
int port = componentSettings.getAsInt(AD_PORT, 389);
@ -62,13 +64,12 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
* @return An authenticated
*/
@Override
public LdapConnection bind(String userName, char[] password) {
public LdapConnection bind(String userName, SecuredString password) {
String userPrincipal = userName + "@" + this.domainName;
Hashtable<String, Serializable> ldapEnv = new Hashtable<>(this.sharedLdapEnv);
ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapEnv.put(Context.SECURITY_PRINCIPAL, userPrincipal);
ldapEnv.put(Context.SECURITY_CREDENTIALS, password);
ldapEnv.put(Context.SECURITY_CREDENTIALS, password.internalChars());
try {
DirContext ctx = new InitialDirContext(ldapEnv);

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.shield.authc.support.SecuredString;
/**
* This factory holds settings needed for authenticating to LDAP and creating LdapConnections.
* Each created LdapConnection needs to be closed or else connections will pill up consuming resources.
@ -24,6 +26,6 @@ public interface LdapConnectionFactory {
* Password authenticated bind
* @param user name of the user to authenticate the connection with.
*/
LdapConnection bind(String user, char[] password) ;
LdapConnection bind(String user, SecuredString password) ;
}

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -32,7 +33,7 @@ import java.util.*;
*/
public class LdapGroupToRoleMapper extends AbstractComponent {
public static final String ROLE_MAPPING_DEFAULT_FILE_NAME = ".role_mapping";
public static final String DEFAULT_FILE_NAME = ".role_mapping";
public static final String ROLE_MAPPING_FILE_SETTING = "files.role_mapping";
public static final String USE_UNMAPPED_GROUPS_AS_ROLES_SETTING = "unmapped_groups_as_roles";
@ -57,6 +58,14 @@ public class LdapGroupToRoleMapper extends AbstractComponent {
this.listener = listener;
}
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get(ROLE_MAPPING_FILE_SETTING);
if (location == null) {
return ShieldPlugin.resolveConfigFile(env, DEFAULT_FILE_NAME);
}
return Paths.get(location);
}
public static ImmutableMap<LdapName, Set<String>> parseFile(Path path, ESLogger logger) {
if (!Files.exists(path)) {
return ImmutableMap.of();
@ -92,14 +101,6 @@ public class LdapGroupToRoleMapper extends AbstractComponent {
}
}
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get(ROLE_MAPPING_FILE_SETTING);
if (location == null) {
return env.configFile().toPath().resolve(ROLE_MAPPING_DEFAULT_FILE_NAME);
}
return Paths.get(location);
}
/**
* This will map the groupDN's to ES Roles
*/

View File

@ -5,24 +5,42 @@
*/
package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.support.AbstractShieldModule;
import static org.elasticsearch.common.inject.name.Names.named;
/**
* Configures Ldap object injections
*/
public class LdapModule extends AbstractModule {
private final Settings settings;
public class LdapModule extends AbstractShieldModule.Node {
private final boolean enabled;
public LdapModule(Settings settings) {
this.settings = settings;
super(settings);
enabled = enabled(settings);
}
public static boolean enabled(Settings settings) {
@Override
protected void configureNode() {
if (enabled) {
bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton();
bind(LdapGroupToRoleMapper.class).asEagerSingleton();
String mode = settings.getComponentSettings(LdapModule.class).get("mode", "ldap");
if ("ldap".equals(mode)) {
bind(LdapConnectionFactory.class).to(StandardLdapConnectionFactory.class);
} else {
bind(LdapConnectionFactory.class).to(ActiveDirectoryConnectionFactory.class);
}
} else {
bind(LdapRealm.class).toProvider(Providers.of((LdapRealm) null));
}
}
static boolean enabled(Settings settings) {
Settings authcSettings = settings.getAsSettings("shield.authc");
if (!authcSettings.names().contains("ldap")) {
return false;
@ -30,16 +48,4 @@ public class LdapModule extends AbstractModule {
Settings ldapSettings = authcSettings.getAsSettings("ldap");
return ldapSettings.getAsBoolean("enabled", true);
}
@Override
protected void configure() {
bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton();
bind(LdapGroupToRoleMapper.class).asEagerSingleton();
String mode = settings.getComponentSettings(LdapModule.class).get("mode", "ldap");
if ("ldap".equals(mode)) {
bind(LdapConnectionFactory.class).to(StandardLdapConnectionFactory.class);
} else {
bind(LdapConnectionFactory.class).to(ActiveDirectoryConnectionFactory.class);
}
}
}

View File

@ -7,16 +7,15 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.SecurityException;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.transport.TransportMessage;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@ -25,21 +24,17 @@ import java.util.Set;
*/
public class LdapRealm extends CachingUsernamePasswordRealm implements Realm<UsernamePasswordToken> {
static {
BaseRestHandler.addUsefulHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
public static final String TYPE = "ldap";
private final LdapConnectionFactory connectionFactory;
private final LdapGroupToRoleMapper roleMapper;
@Inject
public LdapRealm(Settings settings, LdapConnectionFactory ldap, LdapGroupToRoleMapper roleMapper) {
public LdapRealm(Settings settings, LdapConnectionFactory ldap, LdapGroupToRoleMapper roleMapper, RestController restController) {
super(settings);
this.connectionFactory = ldap;
this.roleMapper = roleMapper;
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
@Override
@ -66,9 +61,8 @@ public class LdapRealm extends CachingUsernamePasswordRealm implements Realm<Use
List<String> groupDNs = session.getGroups();
Set<String> roles = roleMapper.mapRoles(groupDNs);
User.Simple user = new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));
Arrays.fill(token.credentials(), '\0');
return user;
} catch (SecurityException e){
} catch (ShieldException e){
logger.info("Authentication Failed for user [{}]", e, token.principal());
return null;
}

View File

@ -10,6 +10,8 @@ import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.authc.support.SecuredString;
import javax.naming.Context;
import javax.naming.NamingException;
@ -43,11 +45,11 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements
super(settings);
userDnTemplates = componentSettings.getAsArray(USER_DN_TEMPLATES_SETTING);
if (userDnTemplates == null) {
throw new org.elasticsearch.shield.SecurityException("Missing required ldap setting [" + USER_DN_TEMPLATES_SETTING + "]");
throw new ShieldException("Missing required ldap setting [" + USER_DN_TEMPLATES_SETTING + "]");
}
String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING);
if (ldapUrls == null) {
throw new org.elasticsearch.shield.SecurityException("Missing required ldap setting [" + URLS_SETTING + "]");
throw new ShieldException("Missing required ldap setting [" + URLS_SETTING + "]");
}
sharedLdapEnv = ImmutableMap.<String, Serializable>builder()
.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
@ -67,11 +69,11 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements
* @return authenticated exception
*/
@Override
public LdapConnection bind(String username, char[] password) {
public LdapConnection bind(String username, SecuredString password) {
//SASL, MD5, etc. all options here stink, we really need to go over ssl + simple authentication
Hashtable<String, Serializable> ldapEnv = new Hashtable<>(this.sharedLdapEnv);
ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapEnv.put(Context.SECURITY_CREDENTIALS, password);
ldapEnv.put(Context.SECURITY_CREDENTIALS, password.internalChars());
for (String template : userDnTemplates) {
String dn = buildDnFromTemplate(username, template);
@ -86,6 +88,7 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements
logger.warn("Failed ldap authentication with user template [{}], dn [{}]", template, dn);
}
}
throw new LdapException("Failed ldap authentication");
}

View File

@ -14,7 +14,6 @@ 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.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
@ -638,13 +637,16 @@ public class BCrypt {
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* Hash a password using the OpenBSD bcrypt scheme.
*
* Modified from the original to take a SecuredString instead of the original
*
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated
* using BCrypt.gensalt)
* @return the hashed password
*/
public static String hashpw(String password, String salt) {
public static String hashpw(SecuredString password, String salt) {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
@ -669,12 +671,19 @@ public class BCrypt {
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
/* original code before introducing SecuredString
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
*/
// the next line is the SecuredString replacement for the above commented-out section
passwordb = ( minor >= 'a' ? password.concat("\000"): password ).utf8Bytes();
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
@ -740,12 +749,14 @@ public class BCrypt {
/**
* Check that a plaintext password matches a previously hashed
* one
* one.
*
* Modified from the original to take a SecuredString plaintext
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
public static boolean checkpw(SecuredString plaintext, String hashed) {
return hashed.compareTo(hashpw(plaintext, hashed)) == 0;
}
}

View File

@ -61,7 +61,7 @@ public abstract class CachingUserPasswdStore extends AbstractComponent implement
}
@Override
public final boolean verifyPassword(final String username, final char[] password) {
public final boolean verifyPassword(final String username, final SecuredString password) {
if (cache == null) {
return doVerifyPassword(username, password);
}
@ -80,7 +80,7 @@ public abstract class CachingUserPasswdStore extends AbstractComponent implement
* Verifies the given password. Both the given username, and if the username is verified, then the
* given password. This method is used when the caching is disabled.
*/
protected abstract boolean doVerifyPassword(String username, char[] password);
protected abstract boolean doVerifyPassword(String username, SecuredString password);
protected abstract PasswordHash passwordHash(String username);
@ -92,8 +92,8 @@ public abstract class CachingUserPasswdStore extends AbstractComponent implement
}
@Override
public final void store(String username, char[] key) {
doStore(username, key);
public final void store(String username, SecuredString password) {
doStore(username, password);
expire(username);
}
@ -103,7 +103,7 @@ public abstract class CachingUserPasswdStore extends AbstractComponent implement
expire(username);
}
protected abstract void doStore(String username, char[] password);
protected abstract void doStore(String username, SecuredString password);
protected abstract void doRemove(String username);
}
@ -113,7 +113,7 @@ public abstract class CachingUserPasswdStore extends AbstractComponent implement
*/
static interface PasswordHash {
boolean verify(char[] password);
boolean verify(SecuredString password);
}
}

View File

@ -116,12 +116,12 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
public static class UserWithHash {
User user;
char[] hash;
public UserWithHash(User user, char[] password){
public UserWithHash(User user, SecuredString password){
this.user = user;
this.hash = Hasher.HTPASSWD.hash(password);
}
public boolean verify(char[] password){
public boolean verify(SecuredString password){
return Hasher.HTPASSWD.verify(password, hash);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.support;
import org.elasticsearch.common.base.Charsets;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
/**
* Helper class similar to Arrays to handle conversions for Char arrays
*/
public class CharArrays {
static char[] utf8BytesToChars(byte[] utf8Bytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(utf8Bytes);
CharBuffer charBuffer = Charsets.UTF_8.decode(byteBuffer);
char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
byteBuffer.clear();
charBuffer.clear();
return chars;
}
/**
* Like String.indexOf for for an array of chars
*/
static int indexOf(char[] array, char ch){
for (int i = 0; (i < array.length); i++) {
if (array[i] == ch) {
return i;
}
}
return -1;
}
public static byte[] toUtf8Bytes(char[] chars) {
CharBuffer charBuffer = CharBuffer.wrap(chars);
ByteBuffer byteBuffer = Charsets.UTF_8.encode(charBuffer);
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
return bytes;
}
}

View File

@ -10,12 +10,8 @@ import org.apache.commons.codec.digest.Crypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.Md5Crypt;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.os.OsUtils;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Locale;
/**
@ -30,25 +26,25 @@ public enum Hasher {
HTPASSWD() {
@Override
public char[] hash(char[] text) {
public char[] hash(SecuredString text) {
String salt = org.elasticsearch.shield.authc.support.BCrypt.gensalt();
return BCrypt.hashpw(new String(text), salt).toCharArray();
return BCrypt.hashpw(text, salt).toCharArray();
}
@Override
public boolean verify(char[] text, char[] hash) {
public boolean verify(SecuredString text, char[] hash) {
String hashStr = new String(hash);
if (hashStr.startsWith(BCRYPT_PREFIX_Y)) {
hashStr = BCRYPT_PREFIX + hashStr.substring(BCRYPT_PREFIX_Y.length());
}
if (hashStr.startsWith(BCRYPT_PREFIX)) {
return BCrypt.checkpw(new String(text), hashStr);
return BCrypt.checkpw(text, hashStr);
}
if (hashStr.startsWith(PLAIN_PREFIX)) {
hashStr = hashStr.substring(PLAIN_PREFIX.length());
return hashStr.compareTo(new String(text)) == 0;
return text.equals(hashStr);
}
byte[] textBytes = toBytes(text);
byte[] textBytes = CharArrays.toUtf8Bytes(text.internalChars());
if (hashStr.startsWith(APR1_PREFIX)) {
return hashStr.compareTo(Md5Crypt.apr1Crypt(textBytes, hashStr)) == 0;
}
@ -57,8 +53,8 @@ public enum Hasher {
return hashStr.substring(SHA1_PREFIX.length()).compareTo(passwd64) == 0;
}
return CRYPT_SUPPORTED ?
hashStr.compareTo(Crypt.crypt(textBytes, hashStr)) == 0: // crypt algo
hashStr.compareTo(new String(text)) == 0; // plain text
hashStr.compareTo(Crypt.crypt(textBytes, hashStr)) == 0 : // crypt algo
text.equals(hashStr); // plain text
}
};
@ -88,16 +84,8 @@ public enum Hasher {
return hasher;
}
public abstract char[] hash(char[] data);
public abstract char[] hash(SecuredString data);
public abstract boolean verify(char[] data, char[] hash);
private static byte[] toBytes(char[] chars) {
CharBuffer charBuffer = CharBuffer.wrap(chars);
ByteBuffer byteBuffer = Charsets.UTF_8.encode(charBuffer);
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
return bytes;
}
public abstract boolean verify(SecuredString data, char[] hash);
}

View File

@ -0,0 +1,158 @@
/*
* 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.support;
import org.elasticsearch.ElasticsearchException;
import java.nio.CharBuffer;
import java.util.Arrays;
/**
* This is not a string but a CharSequence that can be cleared of its memory. Important for handling passwords.
*
* @NotThreadSafe There is a chance that the chars could be cleared while doing operations on the chars.
*
* TODO: dot net's SecureString implementation does some obfuscation of the password to prevent gleaming passwords
* from memory dumps. (this is hard as dot net uses windows system crypto. Thats probably the reason java still doesn't have it)
*/
public class SecuredString implements CharSequence {
private final char[] chars;
private boolean cleared = false;
/**
* Note: the passed in chars are not duplicated, but used directly for performance/optimization. DO NOT
* modify or clear the chars after it has been passed into this constructor.
* @param chars
*/
public SecuredString(char[] chars){
this.chars = new char[chars.length];
System.arraycopy(chars, 0, this.chars, 0, chars.length);
}
/**
* This constructor is used internally for the concatenate method. It DOES duplicate the passed in array, unlike
* the public constructor
*/
private SecuredString(char[] chars, int start, int end){
this.chars = new char[end - start];
System.arraycopy(chars, start, this.chars, 0, this.chars.length);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (o instanceof SecuredString) {
SecuredString that = (SecuredString) o;
if (cleared != that.cleared) return false;
if (!Arrays.equals(chars, that.chars)) return false;
return true;
}
else if (o instanceof CharSequence) {
CharSequence that = (CharSequence) o;
if (cleared) return false;
if (chars.length != that.length()) return false;
for(int i=0; i < chars.length; i++){
if (chars[i] != that.charAt(i)) {
return false;
}
}
return true;
}
return false;
}
@Override
public int hashCode() {
int result = Arrays.hashCode(chars);
result = 31 * result + (cleared ? 1 : 0);
return result;
}
/**
* Note: This is a dangerous call that exists for performance/optimization
* DO NOT modify the array returned by this method. To clear the array call SecureString.clear().
* @return the internal characters that MUST NOT be cleared manually
*/
public char[] internalChars(){
throwIfCleared();
return chars;
}
/**
* @return utf8 encoded bytes
*/
public byte[] utf8Bytes(){
throwIfCleared();
return CharArrays.toUtf8Bytes(chars);
}
@Override
public int length() {
throwIfCleared();
return chars.length;
}
@Override
public char charAt(int index) {
throwIfCleared();
return chars[index];
}
@Override
public SecuredString subSequence(int start, int end) {
throwIfCleared();
return new SecuredString(this.chars, start, end);
}
/**
* Manually clear the underlying array holding the characters
*/
public void clear(){
cleared = true;
Arrays.fill(chars, (char) 0);
}
@Override
public void finalize() throws Throwable{
clear();
super.finalize();
}
public int indexOf(char toFind) {
for(int i=0; i<chars.length; i++){
if (chars[i] == toFind) {
return i;
}
}
return -1;
}
/**
* @param toAppend String to combine with this SecureString
* @return a new SecureString with toAppend concatenated
*/
public SecuredString concat(CharSequence toAppend ) {
throwIfCleared();
CharBuffer buffer = CharBuffer.allocate(chars.length+toAppend.length());
buffer.put(chars);
for(int i = 0; i < toAppend.length(); i++){
buffer.put(i+chars.length, toAppend.charAt(i));
}
return new SecuredString(buffer.array());
}
private void throwIfCleared() {
if (cleared) {
throw new ElasticsearchException("Attempt to use cleared password");
}
}
}

View File

@ -10,11 +10,11 @@ package org.elasticsearch.shield.authc.support;
*/
public interface UserPasswdStore {
boolean verifyPassword(String username, char[] password);
boolean verifyPassword(String username, SecuredString password);
static interface Writable extends UserPasswdStore {
void store(String username, char[] password);
void store(String username, SecuredString password);
void remove(String username);

View File

@ -13,6 +13,7 @@ import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
@ -27,9 +28,9 @@ public class UsernamePasswordToken implements AuthenticationToken {
private static final Pattern BASIC_AUTH_PATTERN = Pattern.compile("Basic\\s(.+)");
private final String username;
private final char[] password;
private final SecuredString password;
public UsernamePasswordToken(String username, char[] password) {
public UsernamePasswordToken(String username, SecuredString password) {
this.username = username;
this.password = password;
}
@ -40,10 +41,15 @@ public class UsernamePasswordToken implements AuthenticationToken {
}
@Override
public char[] credentials() {
public SecuredString credentials() {
return password;
}
@Override
public void clearCredentials() {
password.clear();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -51,15 +57,17 @@ public class UsernamePasswordToken implements AuthenticationToken {
UsernamePasswordToken that = (UsernamePasswordToken) o;
return Arrays.equals(password, that.password) &&
return password.equals(password) &&
Objects.equals(username, that.username);
}
@Override
public int hashCode() {
return Objects.hash(username, Arrays.hashCode(password));
return Objects.hash(username, password.hashCode());
}
public static UsernamePasswordToken extractToken(TransportMessage<?> message, UsernamePasswordToken defaultToken) {
String authStr = message.getHeader(BASIC_AUTH_HEADER);
if (authStr == null) {
@ -71,12 +79,15 @@ public class UsernamePasswordToken implements AuthenticationToken {
throw new AuthenticationException("Invalid basic authentication header value");
}
String userpasswd = new String(Base64.decodeBase64(matcher.group(1)), Charsets.UTF_8);
int i = userpasswd.indexOf(':');
char[] userpasswd = CharArrays.utf8BytesToChars(Base64.decodeBase64(matcher.group(1)));
int i = CharArrays.indexOf(userpasswd, ':');
if (i < 0) {
throw new AuthenticationException("Invalid basic authentication header value");
}
return new UsernamePasswordToken(userpasswd.substring(0, i), userpasswd.substring(i+1).toCharArray());
return new UsernamePasswordToken(
new String(Arrays.copyOfRange(userpasswd, 0, i)),
new SecuredString(Arrays.copyOfRange(userpasswd, i + 1, userpasswd.length)));
}
public static UsernamePasswordToken extractToken(RestRequest request, UsernamePasswordToken defaultToken) {
@ -90,21 +101,27 @@ public class UsernamePasswordToken implements AuthenticationToken {
throw new AuthenticationException("Invalid basic authentication header value");
}
String userpasswd = new String(Base64.decodeBase64(matcher.group(1)), Charsets.UTF_8);
int i = userpasswd.indexOf(':');
char[] userpasswd = CharArrays.utf8BytesToChars(Base64.decodeBase64(matcher.group(1)));
int i = CharArrays.indexOf(userpasswd, ':');
if (i < 0) {
throw new AuthenticationException("Invalid basic authentication header value");
}
return new UsernamePasswordToken(userpasswd.substring(0, i), userpasswd.substring(i+1).toCharArray());
return new UsernamePasswordToken(
new String(Arrays.copyOfRange(userpasswd, 0, i)),
new SecuredString(Arrays.copyOfRange(userpasswd, i + 1, userpasswd.length)));
}
public static void putTokenHeader(TransportRequest request, UsernamePasswordToken token) {
request.putHeader("Authorization", basicAuthHeaderValue(token.username, token.password));
}
public static String basicAuthHeaderValue(String username, char[] passwd) {
String basicToken = username + ":" + new String(passwd);
basicToken = new String(Base64.encodeBase64(basicToken.getBytes(Charsets.UTF_8)), Charsets.UTF_8);
public static String basicAuthHeaderValue(String username, SecuredString passwd) {
CharBuffer chars = CharBuffer.allocate(username.length() + passwd.length() + 1);
chars.put(username).put(':').put(passwd.internalChars());
//TODO we still have passwords in Strings in headers
String basicToken = new String(Base64.encodeBase64(CharArrays.toUtf8Bytes(chars.array())), Charsets.UTF_8);
return "Basic " + basicToken;
}
}

View File

@ -5,11 +5,12 @@
*/
package org.elasticsearch.shield.authc.system;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.transport.TransportMessage;
/**
@ -27,6 +28,11 @@ public class SystemRealm implements Realm<AuthenticationToken> {
public Object credentials() {
return null;
}
@Override
public void clearCredentials() {
}
};
@Override
@ -58,9 +64,14 @@ public class SystemRealm implements Realm<AuthenticationToken> {
return token == TOKEN ? User.SYSTEM : null;
}
public static class Module extends AbstractModule {
public static class Module extends AbstractShieldModule.Node {
public Module(Settings settings) {
super(settings);
}
@Override
protected void configure() {
protected void configureNode() {
bind(SystemRealm.class).asEagerSingleton();
}
}

View File

@ -6,11 +6,12 @@
package org.elasticsearch.shield.authz;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.ShieldException;
/**
*
*/
public class AuthorizationException extends org.elasticsearch.shield.SecurityException {
public class AuthorizationException extends ShieldException {
public AuthorizationException(String msg) {
super(msg);

View File

@ -5,17 +5,22 @@
*/
package org.elasticsearch.shield.authz;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.shield.authz.store.RolesStore;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class AuthorizationModule extends AbstractModule {
public class AuthorizationModule extends AbstractShieldModule.Node {
public AuthorizationModule(Settings settings) {
super(settings);
}
@Override
protected void configure() {
protected void configureNode() {
bind(RolesStore.class).to(FileRolesStore.class);
bind(AuthorizationService.class).to(InternalAuthorizationService.class).asEagerSingleton();
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.shield.authz;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.collect.ImmutableList;
@ -187,11 +188,22 @@ public interface Permission {
@Override @SuppressWarnings("unchecked")
public boolean check(String action, TransportRequest request, MetaData metaData) {
Set<String> indices = Collections.emptySet();
for (IndicesResolver resolver : indicesResolvers) {
if (resolver.requestType().isInstance(request)) {
indices = resolver.resolve(request, metaData);
break;
// some APIs are indices requests that are not actually associated with indices. For example,
// search scroll request, is categorized under the indices context, but doesn't hold indices names
// (in this case, the security check on the indices was done on the search request that initialized
// the scroll... and we rely on the signed scroll id to provide security over this request).
//
// so we only check indices if indeed the request is an actual IndicesRequest, if it's not, we only
// perform the check on the action name.
Set<String> indices = null;
if (request instanceof IndicesRequest) {
indices = Collections.emptySet();
for (IndicesResolver resolver : indicesResolvers) {
if (resolver.requestType().isInstance(request)) {
indices = resolver.resolve(request, metaData);
break;
}
}
}
@ -232,9 +244,11 @@ public interface Permission {
return false;
}
for (String index : indices) {
if (!indexNameMatcher.apply(index)) {
return false;
if (indices != null) {
for (String index : indices) {
if (!indexNameMatcher.apply(index)) {
return false;
}
}
}

View File

@ -28,7 +28,7 @@ public class DefaultIndicesResolver implements IndicesResolver<TransportRequest>
public Set<String> resolve(TransportRequest request, MetaData metaData) {
boolean isIndicesRequest = request instanceof CompositeIndicesRequest || request instanceof IndicesRequest;
assert isIndicesRequest : "the only requests passing the action matcher should be IndicesRequests";
assert isIndicesRequest : "Request [" + request + "] is not an Indices request. 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) {

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.jackson.dataformat.yaml.snakeyaml.error.YAMLException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
@ -20,6 +21,7 @@ import org.elasticsearch.common.xcontent.yaml.YamlXContent;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authz.Permission;
import org.elasticsearch.shield.authz.Privilege;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -57,7 +59,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
super(settings);
file = resolveFile(componentSettings, env);
permissions = parseFile(file);
permissions = parseFile(file, logger);
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
watcher.addListener(new FileListener());
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
@ -72,12 +74,17 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get("files.roles");
if (location == null) {
return env.configFile().toPath().resolve(".roles.yml");
return ShieldPlugin.resolveConfigFile(env, ".roles.yml");
}
return Paths.get(location);
}
public static ImmutableMap<String, Permission.Global> parseFile(Path path) {
public static ImmutableMap<String, Permission.Global> parseFile(Path path, ESLogger logger) {
if (logger != null) {
logger.trace("Reading roles file located at [{}]", path);
}
if (!Files.exists(path)) {
return ImmutableMap.of();
}
@ -218,7 +225,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
@Override
public void onFileChanged(File file) {
if (file.equals(FileRolesStore.this.file.toFile())) {
permissions = parseFile(file.toPath());
permissions = parseFile(file.toPath(), logger);
listener.onRefresh();
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.key;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldException;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;
/**
*
*/
public class InternalKeyService extends AbstractComponent implements KeyService {
public static final String FILE_SETTING = "shield.system_key.file";
public static final String KEY_ALGO = "HmacSHA512";
public static final int KEY_SIZE = 1024;
static final String FILE_NAME = ".system_key";
static final String HMAC_ALGO = "HmacSHA1";
private static final Pattern SIG_PATTERN = Pattern.compile("\\$\\$[0-9]+\\$\\$.+");
private final Path keyFile;
private volatile SecretKey key;
@Inject
public InternalKeyService(Settings settings, Environment env, ResourceWatcherService watcherService) {
this(settings, env, watcherService, Listener.NOOP);
}
InternalKeyService(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
super(settings);
keyFile = resolveFile(settings, env);
key = readKey(keyFile);
FileWatcher watcher = new FileWatcher(keyFile.getParent().toFile());
watcher.addListener(new FileListener(listener));
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
}
public static byte[] generateKey() throws Exception {
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGO);
generator.init(KEY_SIZE);
return generator.generateKey().getEncoded();
}
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get(FILE_SETTING);
if (location == null) {
return ShieldPlugin.resolveConfigFile(env, FILE_NAME);
}
return Paths.get(location);
}
static SecretKey readKey(Path file) {
if (!Files.exists(file)) {
return null;
}
try {
byte[] bytes = Files.readAllBytes(file);
return new SecretKeySpec(bytes, KEY_ALGO);
} catch (IOException e) {
throw new ShieldException("Could not read secret key", e);
}
}
@Override
public String sign(String text) {
SecretKey key = this.key;
if (key == null) {
return text;
}
String sigStr = signInternal(text);
return "$$" + sigStr.length() + "$$" + sigStr + text;
}
@Override
public String unsignAndVerify(String signedText) {
SecretKey key = this.key;
if (key == null) {
return signedText;
}
if (!signedText.startsWith("$$")) {
throw new SignatureException("tampered signed text");
}
try {
// $$34$$sigtext
int i = signedText.indexOf("$$", 2);
int length = Integer.parseInt(signedText.substring(2, i));
String sigStr = signedText.substring(i + 2, i + 2 + length);
String text = signedText.substring(i + 2 + length);
String sig = signInternal(text);
if (!sig.equals(sigStr)) {
throw new SignatureException("the signed texts don't match");
}
return text;
} catch (SignatureException e) {
throw e;
} catch (Throwable t) {
throw new SignatureException("error while verifying the signed text", t);
}
}
@Override
public boolean signed(String text) {
return SIG_PATTERN.matcher(text).matches();
}
static Mac createMac(SecretKey key) {
try {
Mac mac = Mac.getInstance(HMAC_ALGO);
mac.init(key);
return mac;
} catch (Exception e) {
throw new ElasticsearchException("Could not initialize mac", e);
}
}
private String signInternal(String text) {
Mac mac = createMac(key);
byte[] sig = mac.doFinal(text.getBytes(Charsets.UTF_8));
return Base64.encodeBase64URLSafeString(sig);
}
private class FileListener extends FileChangesListener {
private final Listener listener;
private FileListener(Listener listener) {
this.listener = listener;
}
@Override
public void onFileCreated(File file) {
if (file.equals(keyFile.toFile())) {
key = readKey(file.toPath());
listener.onKeyRefresh();
}
}
@Override
public void onFileDeleted(File file) {
if (file.equals(keyFile.toFile())) {
logger.error("System key file was removed! As long as the system key file is missing, elasticsearch won't function as expected for some requests (e.g. scroll/scan)");
key = null;
}
}
@Override
public void onFileChanged(File file) {
if (file.equals(keyFile.toFile())) {
key = readKey(file.toPath());
listener.onKeyRefresh();
}
}
}
static interface Listener {
final Listener NOOP = new Listener() {
@Override
public void onKeyRefresh() {
}
};
void onKeyRefresh();
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.key;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class KeyModule extends AbstractShieldModule.Node {
public KeyModule(Settings settings) {
super(settings);
}
@Override
protected void configureNode() {
bind(KeyService.class).to(InternalKeyService.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.key;
/**
*
*/
public interface KeyService {
/**
* Signs the given text and returns the signed text (original text + signature)
*/
String sign(String text);
/**
* Unsigns the given signed text, verifies the original text with the attached signature and if valid returns
* the unsigned (original) text. If signature verification fails a {@link SignatureException} is thrown.
*/
String unsignAndVerify(String text);
/**
* Checks whether the given text is signed.
*/
boolean signed(String text);
}

View File

@ -0,0 +1,22 @@
/*
* 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.key;
import org.elasticsearch.shield.authz.AuthorizationException;
/**
*
*/
public class SignatureException extends AuthorizationException {
public SignatureException(String msg) {
super(msg);
}
public SignatureException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.key.tool;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.key.InternalKeyService;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.config;
/**
*
*/
public class SystemKeyTool extends CliTool {
public static void main(String[] args) throws Exception {
new SystemKeyTool().execute(args);
}
private static final CliToolConfig CONFIG = config("syskey", SystemKeyTool.class)
.cmds(Generate.CMD)
.build();
public SystemKeyTool() {
super(CONFIG);
}
@Override
protected Command parse(String cmd, CommandLine commandLine) throws Exception {
return Generate.parse(terminal, commandLine);
}
static class Generate extends Command {
private static final String NAME = "generate";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Generate.class).build();
final Path path;
Generate(Terminal terminal, Path path) {
super(terminal);
this.path = path;
}
public static Command parse(Terminal terminal, CommandLine cl) {
String[] args = cl.getArgs();
if (args.length > 1) {
return exitCmd(ExitStatus.USAGE, terminal, "Too many arguments");
}
Path path = args.length != 0 ? Paths.get(args[0]) : null;
return new Generate(terminal, path);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
Path path = this.path;
if (path == null) {
path = InternalKeyService.resolveFile(settings, env);
}
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
byte[] key = InternalKeyService.generateKey();
terminal.println("Storing generated key in [%s]", path.toAbsolutePath());
Files.write(path, key, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
return ExitStatus.OK;
}
}
}

View File

@ -7,15 +7,18 @@ package org.elasticsearch.shield.plugin;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.shield.SecurityModule;
import org.elasticsearch.shield.ShieldModule;
import java.io.File;
import java.nio.file.Path;
import java.util.Collection;
/**
*
*/
public class SecurityPlugin extends AbstractPlugin {
public class ShieldPlugin extends AbstractPlugin {
public static final String NAME = "shield";
@ -31,7 +34,15 @@ public class SecurityPlugin extends AbstractPlugin {
@Override
public Collection<Class<? extends Module>> modules() {
return ImmutableList.<Class<? extends Module>>of(SecurityModule.class);
return ImmutableList.<Class<? extends Module>>of(ShieldModule.class);
}
public static Path configDir(Environment env) {
return new File(env.configFile(), NAME).toPath();
}
public static Path resolveConfigFile(Environment env, String name) {
return configDir(env).resolve(name);
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.support;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.SpawnModules;
import org.elasticsearch.common.settings.Settings;
/**
*
*/
public abstract class AbstractShieldModule extends AbstractModule {
protected final Settings settings;
protected final boolean clientMode;
public AbstractShieldModule(Settings settings) {
this.settings = settings;
this.clientMode = !"node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
}
@Override
protected final void configure() {
configure(clientMode);
}
protected abstract void configure(boolean clientMode);
public static abstract class Spawn extends AbstractShieldModule implements SpawnModules {
protected Spawn(Settings settings) {
super(settings);
}
@Override
public final Iterable<? extends Module> spawnModules() {
return spawnModules(clientMode);
}
public abstract Iterable<? extends Module> spawnModules(boolean clientMode);
}
public static abstract class Node extends AbstractShieldModule {
protected Node(Settings settings) {
super(settings);
}
@Override
protected final void configure(boolean clientMode) {
assert !clientMode : "[" + getClass().getSimpleName() + "] is a node only module";
configureNode();
}
protected abstract void configureNode();
public static abstract class Spawn extends Node implements SpawnModules {
protected Spawn(Settings settings) {
super(settings);
}
public abstract Iterable<? extends AbstractShieldModule.Node> spawnModules();
}
}
}

View File

@ -5,16 +5,21 @@
*/
package org.elasticsearch.shield.transport;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.SecurityFilter;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class SecuredRestModule extends AbstractModule {
public class SecuredRestModule extends AbstractShieldModule.Node {
public SecuredRestModule(Settings settings) {
super(settings);
}
@Override
protected void configure() {
protected void configureNode() {
bind(SecurityFilter.Rest.class).asEagerSingleton();
}
}

View File

@ -6,12 +6,13 @@
package org.elasticsearch.shield.transport;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.PreProcessModule;
import org.elasticsearch.common.inject.SpawnModules;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.SecurityFilter;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.shield.transport.n2n.IPFilteringN2NAuthenticator;
import org.elasticsearch.shield.transport.netty.N2NNettyUpstreamHandler;
import org.elasticsearch.shield.transport.netty.NettySecuredHttpServerTransportModule;
@ -21,28 +22,46 @@ import org.elasticsearch.transport.TransportModule;
/**
*
*/
public class SecuredTransportModule extends AbstractModule implements SpawnModules, PreProcessModule {
public class SecuredTransportModule extends AbstractShieldModule.Spawn implements PreProcessModule {
public SecuredTransportModule(Settings settings) {
super(settings);
}
@Override
public Iterable<? extends Module> spawnModules() {
public Iterable<? extends Module> spawnModules(boolean clientMode) {
if (clientMode) {
return ImmutableList.of(new NettySecuredTransportModule(settings));
}
//todo we only need to spawn http module if we're not within the transport client context
return ImmutableList.of(
new NettySecuredHttpServerTransportModule(),
new NettySecuredTransportModule());
new NettySecuredHttpServerTransportModule(settings),
new NettySecuredTransportModule(settings));
}
@Override
public void processModule(Module module) {
if (module instanceof TransportModule) {
((TransportModule) module).setTransportService(SecuredTransportService.class, SecurityPlugin.NAME);
((TransportModule) module).setTransportService(SecuredTransportService.class, ShieldPlugin.NAME);
}
}
@Override
protected void configure() {
protected void configure(boolean clientMode) {
if (clientMode) {
// no ip filtering on the client
bind(N2NNettyUpstreamHandler.class).toProvider(Providers.<N2NNettyUpstreamHandler>of(null));
return;
}
bind(TransportFilter.class).to(SecurityFilter.Transport.class).asEagerSingleton();
bind(IPFilteringN2NAuthenticator.class).asEagerSingleton();
bind(N2NNettyUpstreamHandler.class).asEagerSingleton();
if (settings.getAsBoolean("shield.transport.n2n.ip_filter.enabled", true)) {
bind(IPFilteringN2NAuthenticator.class).asEagerSingleton();
bind(N2NNettyUpstreamHandler.class).asEagerSingleton();
} else {
bind(N2NNettyUpstreamHandler.class).toProvider(Providers.<N2NNettyUpstreamHandler>of(null));
}
}
}

View File

@ -20,7 +20,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -60,8 +60,7 @@ public class IPFilteringN2NAuthenticator extends AbstractComponent implements N2
private Path resolveFile(Settings settings, Environment env) {
String location = settings.get("ip_filter.file");
if (location == null) {
File shieldDirectory = new File(env.configFile(), SecurityPlugin.NAME);
return shieldDirectory.toPath().resolve(DEFAULT_FILE);
return ShieldPlugin.resolveConfigFile(env, DEFAULT_FILE);
}
return Paths.get(location);
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.netty.channel.ChannelPipeline;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
@ -27,7 +28,7 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
@Inject
public NettySecuredHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays,
N2NNettyUpstreamHandler shieldUpstreamHandler) {
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler) {
super(settings, networkService, bigArrays);
this.ssl = settings.getAsBoolean("shield.http.ssl", false);
this.shieldUpstreamHandler = shieldUpstreamHandler;
@ -56,14 +57,14 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (settings.getAsBoolean("shield.transport.n2n.ip_filter.enabled", true)) {
pipeline.addFirst("ipfilter", shieldUpstreamHandler);
}
if (ssl) {
SSLEngine engine = sslConfig.createSSLEngine();
engine.setUseClientMode(false);
pipeline.addFirst("ssl", new SslHandler(engine));
}
if (shieldUpstreamHandler != null) {
pipeline.addFirst("ipfilter", shieldUpstreamHandler);
}
return pipeline;
}
}

View File

@ -5,24 +5,30 @@
*/
package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.PreProcessModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.http.HttpServerModule;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
*
*/
public class NettySecuredHttpServerTransportModule extends AbstractModule implements PreProcessModule {
public class NettySecuredHttpServerTransportModule extends AbstractShieldModule implements PreProcessModule {
public NettySecuredHttpServerTransportModule(Settings settings) {
super(settings);
}
@Override
public void processModule(Module module) {
if (module instanceof HttpServerModule) {
((HttpServerModule) module).setHttpServerTransport(NettySecuredHttpServerTransport.class, SecurityPlugin.NAME);
((HttpServerModule) module).setHttpServerTransport(NettySecuredHttpServerTransport.class, ShieldPlugin.NAME);
}
}
@Override
protected void configure() {}
protected void configure(boolean clientMode) {
}
}

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.Version;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.netty.channel.ChannelPipeline;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
@ -29,7 +30,7 @@ public class NettySecuredTransport extends NettyTransport {
@Inject
public NettySecuredTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, Version version,
N2NNettyUpstreamHandler shieldUpstreamHandler) {
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler) {
super(settings, threadPool, networkService, bigArrays, version);
this.shieldUpstreamHandler = shieldUpstreamHandler;
this.ssl = settings.getAsBoolean("shield.transport.ssl", false);
@ -63,9 +64,6 @@ public class NettySecuredTransport extends NettyTransport {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (settings.getAsBoolean("shield.transport.n2n.ip_filter.enabled", true)) {
pipeline.addFirst("ipfilter", shieldUpstreamHandler);
}
if (ssl) {
SSLEngine serverEngine = sslConfig.createSSLEngine();
serverEngine.setUseClientMode(false);
@ -73,6 +71,9 @@ public class NettySecuredTransport extends NettyTransport {
pipeline.addFirst("ssl", new SslHandler(serverEngine));
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, logger));
}
if (shieldUpstreamHandler != null) {
pipeline.addFirst("ipfilter", shieldUpstreamHandler);
}
return pipeline;
}
}

View File

@ -5,25 +5,30 @@
*/
package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.PreProcessModule;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.transport.TransportModule;
/**
*
*/
public class NettySecuredTransportModule extends AbstractModule implements PreProcessModule {
public class NettySecuredTransportModule extends AbstractShieldModule implements PreProcessModule {
public NettySecuredTransportModule(Settings settings) {
super(settings);
}
@Override
public void processModule(Module module) {
if (module instanceof TransportModule) {
((TransportModule) module).setTransport(NettySecuredTransport.class, SecurityPlugin.NAME);
((TransportModule) module).setTransport(NettySecuredTransport.class, ShieldPlugin.NAME);
}
}
@Override
protected void configure() {}
protected void configure(boolean clientMode) {}
}

View File

@ -22,8 +22,7 @@ import java.util.Arrays;
public class SSLConfig {
private static final ESLogger logger = Loggers.getLogger(SSLConfig.class);
// TODO removing the second one results in fails, need to verify the differences, maybe per JVM?
static final String[] DEFAULT_CIPHERS = new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA" };
static final String[] DEFAULT_CIPHERS = new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" };
private final boolean clientAuth;
private SSLContext sslContext;

View File

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

View File

@ -0,0 +1,22 @@
NAME
syskeygen - Generates the system key
SYNOPSIS
syskeygen [<file>]
DESCRIPTION
Generates the system key and stores in the system_key file. By default
it will be stored in 'config/shield/.system_key' file. If the file location
is customized in the elasticsearch.yml (under the 'shield.system_key.file'
setting), the generated key will be stored in that custom location.
It is also possible to pass the file location (where the key will be stored)
as an argument to this command.
ARGUMENTS
<file> (optional) Specifies the file where the generated key will be
written to

View File

@ -0,0 +1,138 @@
/*
* 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.ExceptionsHelper;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.key.InternalKeyService;
import org.elasticsearch.shield.key.KeyService;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
*
*/
public class ScrollIdSigningTests extends ShieldIntegrationTest {
private KeyService keyService;
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(InternalKeyService.FILE_SETTING, writeFile(newFolder(), "system_key", generateKey()))
.build();
}
@Before
public void init() throws Exception {
keyService = internalCluster().getInstance(KeyService.class);
}
@Test
public void testSearchAndClearScroll() throws Exception {
IndexRequestBuilder[] docs = new IndexRequestBuilder[randomIntBetween(20, 100)];
for (int i = 0; i < docs.length; i++) {
docs[i] = client().prepareIndex("idx", "type").setSource("field", "value");
}
indexRandom(true, docs);
SearchResponse response = client().prepareSearch()
.setQuery(matchAllQuery())
.setScroll(TimeValue.timeValueMinutes(2))
.setSize(randomIntBetween(1, 10)).get();
assertHitCount(response, docs.length);
int hits = response.getHits().hits().length;
try {
assertSigned(response.getScrollId());
while (true) {
response = client().prepareSearchScroll(response.getScrollId())
.setScroll(TimeValue.timeValueMinutes(2)).get();
assertSigned(response.getScrollId());
assertHitCount(response, docs.length);
hits += response.getHits().hits().length;
if (response.getHits().getHits().length == 0) {
break;
}
}
assertThat(hits, equalTo(docs.length));
} finally {
clearScroll(response.getScrollId());
}
}
@Test
public void testSearchScroll_WithTamperedScrollId() throws Exception {
IndexRequestBuilder[] docs = new IndexRequestBuilder[randomIntBetween(20, 100)];
for (int i = 0; i < docs.length; i++) {
docs[i] = client().prepareIndex("idx", "type").setSource("field", "value");
}
indexRandom(true, docs);
SearchResponse response = client().prepareSearch()
.setQuery(matchAllQuery())
.setScroll(TimeValue.timeValueMinutes(2))
.setSize(randomIntBetween(1, 10)).get();
String scrollId = response.getScrollId();
String tamperedScrollId = randomBoolean() ? scrollId.substring(randomIntBetween(1, 10)) : scrollId + randomAsciiOfLength(randomIntBetween(3, 10));
try {
client().prepareSearchScroll(tamperedScrollId).setScroll(TimeValue.timeValueMinutes(2)).get();
fail("Expected an authorization exception to be thrown when scroll id is tampered");
} catch (Exception e) {
assertThat(ExceptionsHelper.unwrap(e, AuthorizationException.class), notNullValue());
} finally {
clearScroll(scrollId);
}
}
@Test
public void testClearScroll_WithTamperedScrollId() throws Exception {
IndexRequestBuilder[] docs = new IndexRequestBuilder[randomIntBetween(20, 100)];
for (int i = 0; i < docs.length; i++) {
docs[i] = client().prepareIndex("idx", "type").setSource("field", "value");
}
indexRandom(true, docs);
SearchResponse response = client().prepareSearch()
.setQuery(matchAllQuery())
.setScroll(TimeValue.timeValueMinutes(2))
.setSize(5).get();
String scrollId = response.getScrollId();
String tamperedScrollId = randomBoolean() ? scrollId.substring(randomIntBetween(1, 10)) : scrollId + randomAsciiOfLength(randomIntBetween(3, 10));
try {
client().prepareClearScroll().addScrollId(tamperedScrollId).get();
fail("Expected an authorization exception to be thrown when scroll id is tampered");
} catch (Exception e) {
assertThat(ExceptionsHelper.unwrap(e, AuthorizationException.class), notNullValue());
} finally {
clearScroll(scrollId);
}
}
private void assertSigned(String scrollId) {
assertThat(keyService.signed(scrollId), is(true));
}
private static byte[] generateKey() {
try {
return InternalKeyService.generateKey();
} catch (Exception e) {
fail("failed to generate key");
return null;
}
}
}

View File

@ -7,18 +7,22 @@ package org.elasticsearch.shield;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestFilterChain;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.AuthenticationException;
import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.system.SystemRealm;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.key.KeyService;
import org.elasticsearch.shield.key.SignatureException;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.junit.Before;
@ -41,13 +45,17 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
private AuthenticationService authcService;
private AuthorizationService authzService;
private RestController restController;
private KeyService keyService;
private AuditTrail auditTrail;
@Before
public void init() throws Exception {
authcService = mock(AuthenticationService.class);
authzService = mock(AuthorizationService.class);
restController = mock(RestController.class);
filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService);
keyService = mock(KeyService.class);
auditTrail = mock(AuditTrail.class);
filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService, keyService, auditTrail);
}
@Test
@ -57,7 +65,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
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);
filter.authenticateAndAuthorize("_action", request);
verify(authzService).authorize(user, "_action", request);
}
@ -68,7 +76,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
User user = new User.Simple("_username", "r1");
when(authcService.token("internal:_action", request, SystemRealm.TOKEN)).thenReturn(token);
when(authcService.authenticate("internal:_action", request, token)).thenReturn(user);
filter.process("internal:_action", request);
filter.authenticateAndAuthorize("internal:_action", request);
verify(authzService).authorize(user, "internal:_action", request);
}
@ -80,7 +88,18 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
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);
filter.authenticateAndAuthorize("_action", request);
}
@Test
public void testProcess_Rest_AuthenticationFails_Authenticate() throws Exception {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("failed authc");
RestRequest request = mock(RestRequest.class);
AuthenticationToken token = mock(AuthenticationToken.class);
when(authcService.token(request)).thenReturn(token);
when(authcService.authenticate(request, token)).thenThrow(new AuthenticationException("failed authc"));
filter.authenticate(request);
}
@Test
@ -89,9 +108,19 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
thrown.expectMessage("failed authc");
TransportRequest request = new InternalRequest();
when(authcService.token("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
filter.process("_action", request);
filter.authenticateAndAuthorize("_action", request);
}
@Test
public void testProcess_Rest_AuthenticationFails_NoToken() throws Exception {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("failed authc");
RestRequest request = mock(RestRequest.class);
when(authcService.token(request)).thenThrow(new AuthenticationException("failed authc"));
filter.authenticate(request);
}
@Test
public void testProcess_AuthorizationFails() throws Exception {
thrown.expect(AuthorizationException.class);
@ -102,7 +131,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
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);
filter.authenticateAndAuthorize("_action", request);
}
@Test
@ -111,7 +140,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
SecurityFilter.Transport transport = new SecurityFilter.Transport(filter);
InternalRequest request = new InternalRequest();
transport.inboundRequest("_action", request);
verify(filter).process("_action", request);
verify(filter).authenticateAndAuthorize("_action", request);
}
@Test
@ -121,7 +150,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
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);
doThrow(new RuntimeException("process-error")).when(filter).authenticateAndAuthorize("_action", request);
transport.inboundRequest("_action", request);
}
@ -132,9 +161,10 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
ActionRequest request = mock(ActionRequest.class);
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
when(filter.unsign(any(User.class), eq("_action"), eq(request))).thenReturn(request);
action.apply("_action", request, listener, chain);
verify(filter).process("_action", request);
verify(chain).proceed("_action", request, listener);
verify(filter).authenticateAndAuthorize("_action", request);
verify(chain).proceed(eq("_action"), eq(request), isA(SecurityFilter.SigningListener.class));
}
@Test
@ -145,12 +175,31 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
RuntimeException exception = new RuntimeException("process-error");
doThrow(exception).when(filter).process("_action", request);
doThrow(exception).when(filter).authenticateAndAuthorize("_action", request);
action.apply("_action", request, listener, chain);
verify(listener).onFailure(exception);
verifyNoMoreInteractions(chain);
}
@Test
public void testAction_SignatureError() throws Exception {
SecurityFilter.Action action = new SecurityFilter.Action(filter);
SearchScrollRequest request = new SearchScrollRequest("scroll_id");
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
SignatureException sigException = new SignatureException("bad bad boy");
User user = mock(User.class);
AuthenticationToken token = mock(AuthenticationToken.class);
when(authcService.token("_action", request, null)).thenReturn(token);
when(authcService.authenticate("_action", request, token)).thenReturn(user);
when(keyService.signed("scroll_id")).thenReturn(true);
doThrow(sigException).when(keyService).unsignAndVerify("scroll_id");
action.apply("_action", request, listener, chain);
verify(listener).onFailure(isA(AuthorizationException.class));
verify(auditTrail).tamperedRequest(user, "_action", request);
verifyNoMoreInteractions(chain);
}
@Test
public void testRest_WithToken() throws Exception {
SecurityFilter.Rest rest = new SecurityFilter.Rest(filter, restController);
@ -158,7 +207,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
RestChannel channel = mock(RestChannel.class);
RestFilterChain chain = mock(RestFilterChain.class);
rest.process(request, channel, chain);
verify(authcService).extractAndRegisterToken(request);
verify(authcService).token(request);
verify(restController).registerFilter(rest);
}
@ -171,7 +220,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
RestRequest request = mock(RestRequest.class);
RestChannel channel = mock(RestChannel.class);
RestFilterChain chain = mock(RestFilterChain.class);
doThrow(exception).when(authcService).extractAndRegisterToken(request);
doThrow(exception).when(authcService).token(request);
rest.process(request, channel, chain);
verify(restController).registerFilter(rest);
}

View File

@ -24,6 +24,7 @@ public class AuditTrailModuleTests extends ElasticsearchTestCase {
@Test
public void testEnabled() throws Exception {
Settings settings = ImmutableSettings.builder()
.put("client.type", "node")
.put("shield.audit.enabled", false)
.build();
Injector injector = Guice.createInjector(new SettingsModule(settings), new AuditTrailModule(settings));
@ -33,7 +34,8 @@ public class AuditTrailModuleTests extends ElasticsearchTestCase {
@Test
public void testDisabledByDefault() throws Exception {
Settings settings = ImmutableSettings.EMPTY;
Settings settings = ImmutableSettings.builder()
.put("client.type", "node").build();
Injector injector = Guice.createInjector(new SettingsModule(settings), new AuditTrailModule(settings));
AuditTrail auditTrail = injector.getInstance(AuditTrail.class);
assertThat(auditTrail, is(AuditTrail.NOOP));
@ -43,6 +45,7 @@ public class AuditTrailModuleTests extends ElasticsearchTestCase {
public void testLogfile() throws Exception {
Settings settings = ImmutableSettings.builder()
.put("shield.audit.enabled", true)
.put("client.type", "node")
.build();
Injector injector = Guice.createInjector(new SettingsModule(settings), new AuditTrailModule(settings));
AuditTrail auditTrail = injector.getInstance(AuditTrail.class);
@ -58,6 +61,7 @@ public class AuditTrailModuleTests extends ElasticsearchTestCase {
Settings settings = ImmutableSettings.builder()
.put("shield.audit.enabled", true)
.put("shield.audit.outputs" , "foo")
.put("client.type", "node")
.build();
try {
Guice.createInjector(new SettingsModule(settings), new AuditTrailModule(settings));

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.audit;
import com.google.common.collect.ImmutableSet;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.test.ElasticsearchTestCase;
@ -17,6 +18,7 @@ import org.junit.Test;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;
/**
@ -29,6 +31,7 @@ public class AuditTrailServiceTests extends ElasticsearchTestCase {
private AuthenticationToken token;
private TransportMessage message;
private RestRequest restRequest;
@Before
public void init() throws Exception {
@ -40,6 +43,7 @@ public class AuditTrailServiceTests extends ElasticsearchTestCase {
service = new AuditTrailService(ImmutableSettings.EMPTY, auditTrails);
token = mock(AuthenticationToken.class);
message = mock(TransportMessage.class);
restRequest = mock(RestRequest.class);
}
@Test
@ -50,6 +54,14 @@ public class AuditTrailServiceTests extends ElasticsearchTestCase {
}
}
@Test
public void testAuthenticationFailed_Rest() throws Exception {
service.authenticationFailed(token, restRequest);
for (AuditTrail auditTrail : auditTrails) {
verify(auditTrail).authenticationFailed(token, restRequest);
}
}
@Test
public void testAuthenticationFailed_Realm() throws Exception {
service.authenticationFailed("_realm", token, "_action", message);
@ -58,6 +70,14 @@ public class AuditTrailServiceTests extends ElasticsearchTestCase {
}
}
@Test
public void testAuthenticationFailed_Rest_Realm() throws Exception {
service.authenticationFailed("_realm", token, restRequest);
for (AuditTrail auditTrail : auditTrails) {
verify(auditTrail).authenticationFailed("_realm", token, restRequest);
}
}
@Test
public void testAnonymousAccess() throws Exception {
service.anonymousAccess("_action", message);

View File

@ -6,17 +6,21 @@
package org.elasticsearch.shield.audit.logfile;
import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportMessage;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.util.List;
import static org.elasticsearch.shield.audit.logfile.CapturingLogger.Level;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
@ -63,6 +67,29 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
}
}
@Test
public void testAuthenticationFailed_Rest() throws Exception {
for (Level level : Level.values()) {
RestRequest request = mock(RestRequest.class);
when(request.getRemoteAddress()).thenReturn(new InetSocketAddress("_hostname", 9200));
when(request.uri()).thenReturn("_uri");
when(request.toString()).thenReturn("rest_request");
CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.authenticationFailed(new MockToken(), request);
switch (level) {
case ERROR:
case WARN:
case INFO:
assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[_hostname:9200], URI=[_uri], principal=[_principal]");
break;
case DEBUG:
case TRACE:
assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[_hostname:9200], URI=[_uri], principal=[_principal], request=[rest_request]");
}
}
}
@Test
public void testAuthenticationFailed_Realm() throws Exception {
for (Level level : Level.values()) {
@ -82,6 +109,29 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
}
}
@Test
public void testAuthenticationFailed_Realm_Rest() throws Exception {
for (Level level : Level.values()) {
RestRequest request = mock(RestRequest.class);
when(request.getRemoteAddress()).thenReturn(new InetSocketAddress("_hostname", 9200));
when(request.uri()).thenReturn("_uri");
when(request.toString()).thenReturn("rest_request");
CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.authenticationFailed("_realm", new MockToken(), request);
switch (level) {
case ERROR:
case WARN:
case INFO:
case DEBUG:
assertEmptyLog(logger);
break;
case TRACE:
assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[_hostname:9200], URI=[_uri], principal=[_principal], request=[rest_request]");
}
}
}
@Test
public void testAccessGranted() throws Exception {
for (Level level : Level.values()) {
@ -144,6 +194,7 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
}
}
private static class MockToken implements AuthenticationToken {
@Override
public String principal() {
@ -155,5 +206,10 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
fail("it's not allowed to print the credentials of the auth token");
return null;
}
@Override
public void clearCredentials() {
}
}
}

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.shield.authc;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
@ -16,6 +18,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.Map;
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
@ -41,7 +45,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
public void init() throws Exception {
token = mock(AuthenticationToken.class);
message = new InternalMessage();
restRequest = mock(RestRequest.class);
restRequest = new InternalRestRequest();
firstRealm = mock(Realm.class);
when(firstRealm.type()).thenReturn("first");
secondRealm = mock(Realm.class);
@ -161,24 +165,93 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
}
@Test
public void testExtractAndRegisterToken_Exists() throws Exception {
public void testToken_Rest_Exists() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
when(firstRealm.token(restRequest)).thenReturn(null);
when(secondRealm.token(restRequest)).thenReturn(token);
service.extractAndRegisterToken(restRequest);
AuthenticationToken foundToken = service.token(restRequest);
assertThat(foundToken, is(token));
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_CTX_KEY), equalTo((Object) token));
}
@Test
public void testVerifyToken_Missing() throws Exception {
public void testToken_Rest_Missing() throws Exception {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("Missing authentication token");
when(firstRealm.token(restRequest)).thenReturn(null);
when(secondRealm.token(restRequest)).thenReturn(null);
service.extractAndRegisterToken(restRequest);
service.token(restRequest);
}
private static class InternalMessage extends TransportMessage<InternalMessage> {
}
private static class InternalRestRequest extends RestRequest {
@Override
public Method method() {
return null;
}
@Override
public String uri() {
return "_uri";
}
@Override
public String rawPath() {
return "_path";
}
@Override
public boolean hasContent() {
return false;
}
@Override
public boolean contentUnsafe() {
return false;
}
@Override
public BytesReference content() {
return null;
}
@Override
public String header(String name) {
return null;
}
@Override
public Iterable<Map.Entry<String, String>> headers() {
return ImmutableMap.<String, String>of().entrySet();
}
@Override
public boolean hasParam(String key) {
return false;
}
@Override
public String param(String key) {
return null;
}
@Override
public Map<String, String> params() {
return ImmutableMap.of();
}
@Override
public String param(String key, String defaultValue) {
return null;
}
@Override
public String toString() {
return "rest_request";
}
}
}

View File

@ -11,6 +11,7 @@ import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -21,6 +22,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
/**
*
@ -38,7 +40,8 @@ public class ESUsersModuleTests extends ElasticsearchTestCase {
@Test
public void test() throws Exception {
Injector injector = Guice.createInjector(new TestModule(users, usersRoles), new ESUsersModule());
Settings settings = ImmutableSettings.builder().put("client.type", "node").build();
Injector injector = Guice.createInjector(new TestModule(users, usersRoles), new ESUsersModule(settings));
ESUsersRealm realm = injector.getInstance(ESUsersRealm.class);
assertThat(realm, notNullValue());
assertThat(realm.userPasswdStore, notNullValue());
@ -81,6 +84,7 @@ public class ESUsersModuleTests extends ElasticsearchTestCase {
bind(Environment.class).toInstance(env);
bind(ThreadPool.class).toInstance(new ThreadPool("test"));
bind(ResourceWatcherService.class).asEagerSingleton();
bind(RestController.class).toInstance(mock(RestController.class));
}
}

View File

@ -13,39 +13,54 @@ import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.SecurityFilter;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.support.UserPasswdStore;
import org.elasticsearch.shield.authc.support.UserRolesStore;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.authc.support.*;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.arrayContaining;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
*
*/
public class ESUsersRealmTests extends ElasticsearchTestCase {
private RestController restController;
private Client client;
private AdminClient adminClient;
@Before
public void init() throws Exception {
client = mock(Client.class);
adminClient = mock(AdminClient.class);
restController = mock(RestController.class);
}
@Test
public void testRestHeaderRegistration() {
new ESUsersRealm(ImmutableSettings.EMPTY, mock(UserPasswdStore.class), mock(UserRolesStore.class), restController);
verify(restController).registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
@Test
public void testAuthenticate() throws Exception {
Settings settings = ImmutableSettings.builder().build();
MockUserPasswdStore userPasswdStore = new MockUserPasswdStore("user1", "test123");
MockUserRolesStore userRolesStore = new MockUserRolesStore("user1", "role1", "role2");
ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore);
User user = realm.authenticate(new UsernamePasswordToken("user1", "test123".toCharArray()));
ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore, restController);
User user = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
assertTrue(userPasswdStore.called);
assertTrue(userRolesStore.called);
assertThat(user, notNullValue());
@ -60,16 +75,16 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
Settings settings = ImmutableSettings.builder().build();
MockUserPasswdStore userPasswdStore = new MockUserPasswdStore("user1", "test123");
MockUserRolesStore userRolesStore = new MockUserRolesStore("user1", "role1", "role2");
ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore);
ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore, restController);
TransportRequest request = new TransportRequest() {};
UsernamePasswordToken.putTokenHeader(request, new UsernamePasswordToken("user1", "test123".toCharArray()));
UsernamePasswordToken.putTokenHeader(request, new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
UsernamePasswordToken token = realm.token(request);
assertThat(token, notNullValue());
assertThat(token.principal(), equalTo("user1"));
assertThat(token.credentials(), notNullValue());
assertThat(new String(token.credentials()), equalTo("test123"));
assertThat(new String(token.credentials().internalChars()), equalTo("test123"));
}
@ -85,10 +100,10 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
}
@Override
public boolean verifyPassword(String username, char[] password) {
public boolean verifyPassword(String username, SecuredString password) {
called = true;
assertThat(username, equalTo(this.username));
assertThat(new String(password), equalTo(this.password));
assertThat(new String(password.internalChars()), equalTo(this.password));
return true;
}
}
@ -116,9 +131,8 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
@Test @SuppressWarnings("unchecked")
public void testRestHeadersAreCopied() throws Exception {
// the required header will be registered only if ESUsersRealm is actually used.
new ESUsersRealm(ImmutableSettings.EMPTY, null, null);
Client client = mock(Client.class);
AdminClient adminClient = mock(AdminClient.class);
new ESUsersRealm(ImmutableSettings.EMPTY, null, null, restController);
when(restController.relevantHeaders()).thenReturn(ImmutableSet.of(UsernamePasswordToken.BASIC_AUTH_HEADER));
when(client.admin()).thenReturn(adminClient);
when(adminClient.cluster()).thenReturn(mock(ClusterAdminClient.class));
when(adminClient.indices()).thenReturn(mock(IndicesAdminClient.class));
@ -128,15 +142,16 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
return null;
}
};
RestRequest restRequest = mock(RestRequest.class);
final Action action = mock(Action.class);
final ActionListener listener = mock(ActionListener.class);
BaseRestHandler handler = new BaseRestHandler(ImmutableSettings.EMPTY, client) {
BaseRestHandler handler = new BaseRestHandler(ImmutableSettings.EMPTY, restController, client) {
@Override
protected void handleRequest(RestRequest restRequest, RestChannel channel, Client client) throws Exception {
client.execute(action, request, listener);
}
};
RestRequest restRequest = mock(RestRequest.class);
when(restRequest.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("foobar");
RestChannel channel = mock(RestChannel.class);
handler.handleRequest(restRequest, channel);

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -82,20 +83,20 @@ public class FileUserPasswdStoreTests extends ElasticsearchTestCase {
}
});
assertTrue(store.verifyPassword("bcrypt", "test123".toCharArray()));
assertTrue(store.verifyPassword("bcrypt", SecuredStringTests.build("test123")));
watcherService.start();
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
writer.newLine();
writer.append("foobar:" + new String(Hasher.HTPASSWD.hash("barfoo".toCharArray())));
writer.append("foobar:" + new String(Hasher.HTPASSWD.hash(SecuredStringTests.build("barfoo"))));
}
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("Waited too long for the updated file to be picked up");
}
assertTrue(store.verifyPassword("foobar", "barfoo".toCharArray()));
assertTrue(store.verifyPassword("foobar", SecuredStringTests.build("barfoo")));
} finally {
if (watcherService != null) {

View File

@ -16,6 +16,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@ -44,7 +45,7 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(command, instanceOf(ESUsersTool.Useradd.class));
ESUsersTool.Useradd cmd = (ESUsersTool.Useradd) command;
assertThat(cmd.username, equalTo("username"));
assertThat(new String(cmd.passwd), equalTo("changeme"));
assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme"));
assertThat(cmd.roles, notNullValue());
assertThat(cmd.roles, arrayContaining("r1", "r2", "r3"));
}
@ -69,7 +70,7 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(command, instanceOf(ESUsersTool.Useradd.class));
ESUsersTool.Useradd cmd = (ESUsersTool.Useradd) command;
assertThat(cmd.username, equalTo("username"));
assertThat(new String(cmd.passwd), equalTo("changeme"));
assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme"));
assertThat(cmd.roles, notNullValue());
assertThat(cmd.roles.length, is(0));
}
@ -84,7 +85,7 @@ public class ESUsersToolTests extends CliToolTestCase {
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", "changeme".toCharArray(), "r1", "r2");
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
@ -97,7 +98,7 @@ public class ESUsersToolTests extends CliToolTestCase {
String line = lines.get(0);
assertThat(line, startsWith("user1:"));
String hash = line.substring("user1:".length());
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
assertThat(Hasher.HTPASSWD.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true));
assertFileExists(userRolesFile);
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
@ -115,7 +116,7 @@ public class ESUsersToolTests extends CliToolTestCase {
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", "changeme".toCharArray(), "r1", "r2");
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
@ -131,7 +132,7 @@ public class ESUsersToolTests extends CliToolTestCase {
for (String line : lines) {
if (line.startsWith("user1")) {
String hash = line.substring("user1:".length());
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
assertThat(Hasher.HTPASSWD.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true));
}
}
@ -150,7 +151,7 @@ public class ESUsersToolTests extends CliToolTestCase {
.put("shield.authc.esusers.files.users_roles", userRolesFile)
.build();
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", "changeme".toCharArray(), "r1", "r2");
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2");
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR));
@ -246,7 +247,7 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(command, instanceOf(ESUsersTool.Passwd.class));
ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command;
assertThat(cmd.username, equalTo("user1"));
assertThat(new String(cmd.passwd), equalTo("changeme"));
assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme"));
}
@Test
@ -273,7 +274,7 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(command, instanceOf(ESUsersTool.Passwd.class));
ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command;
assertThat(cmd.username, equalTo("user1"));
assertThat(new String(cmd.passwd), equalTo("changeme"));
assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme"));
assertThat(secretRequested.get(), is(true));
}
@ -295,7 +296,7 @@ public class ESUsersToolTests extends CliToolTestCase {
String line = lines.get(0);
assertThat(line, startsWith("user1:"));
String hash = line.substring("user1:".length());
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
assertThat(Hasher.HTPASSWD.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true));
}
@Test

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Ignore;
import org.junit.Test;
@ -21,8 +22,6 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public static final String AD_LDAP_URL = "ldap://54.213.145.20:389";
public static final String PASSWORD = "4joD8LmWcrEfRa&p";
public static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
@Ignore
@Test
public void testAdAuth() {
@ -31,7 +30,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
String userName = "ironman";
LdapConnection ldap = connectionFactory.bind(userName, PASSWORD.toCharArray());
LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD));
String userDN = ldap.getAuthenticatedUserDn();
//System.out.println("userPassword check:"+ldap.checkPassword(userDn, userPass));
@ -47,10 +46,10 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
String userTemplate = "cn={0},cn=Users,dc=ad,dc=test,dc=elasticsearch,dc=com";
boolean isSubTreeSearch = true;
StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch));
String user = "Tony Stark";
LdapConnection ldap = connectionFactory.bind(user, PASSWORD.toCharArray());
LdapConnection ldap = connectionFactory.bind(user, SecuredStringTests.build(PASSWORD));
List<String> groups = ldap.getGroupsFromUserAttrs(ldap.getAuthenticatedUserDn());
List<String> groups2 = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
@ -62,8 +61,8 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public static Settings buildAdSettings(String ldapUrl, String adDomainName) {
return ImmutableSettings.builder()
.putArray(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.putArray(LdapTest.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(LdapTest.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.build();
}
}

View File

@ -30,6 +30,7 @@ import org.apache.directory.server.i18n.I18n;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.net.URISyntaxException;
@ -40,17 +41,9 @@ import java.util.List;
import java.util.Set;
/**
* Helper Class to start up an Apache DS LDAP server for testing. Here is a typical use example in tests:
* <pre>
* static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif");
* Helper Class to start up an Apache DS LDAP server for testing.
*
* @BeforeClass public static void startServer() throws Exception {
* ldap.startServer();
* }
* @AfterClass public static void stopServer() throws Exception {
* ldap.stopAndCleanup();
* }
* </pre>
* Use ApacheDsRule instead of this class in tests
*/
public class ApacheDsEmbedded {
/**
@ -78,8 +71,8 @@ public class ApacheDsEmbedded {
*
* @throws Exception If something went wrong
*/
public ApacheDsEmbedded(String baseDN, String ldifFileName, String testName) {
this.workDir = new File(System.getProperty("java.io.tmpdir") + "/server-work/" + testName);
public ApacheDsEmbedded(String baseDN, String ldifFileName, File workDir) {
this.workDir = workDir;
this.baseDN = baseDN;
this.ldifFileName = ldifFileName;
this.port = 10389 + CHILD_JVM_ID;
@ -118,7 +111,6 @@ public class ApacheDsEmbedded {
public void stopAndCleanup() throws Exception {
if (server != null) server.stop();
if (service != null) service.shutdown();
workDir.delete();
}
@ -262,8 +254,6 @@ public class ApacheDsEmbedded {
entryApache.add("dc", "Apache");
service.getAdminSession().add(entryApache);
}
// We are all done !
}
private void loadSchema(String ldifFileName) throws URISyntaxException {
@ -274,30 +264,4 @@ public class ApacheDsEmbedded {
LdifFileLoader ldifLoader = new LdifFileLoader(service.getAdminSession(), ldifFile, Collections.EMPTY_LIST);
ldifLoader.execute();
}
/**
* Main class.
*
* @param args Not used.
*/
public static void main(String[] args) {
try {
String baseDir = "o=sevenSeas";
String ldifImport = "seven-seas.ldif";
// Create the server
ApacheDsEmbedded ads = new ApacheDsEmbedded(baseDir, ldifImport, "test");
ads.startServer();
// Read an entry
Entry result = ads.service.getAdminSession().lookup(new Dn(baseDir));
// And print it if available
System.out.println("Found entry : " + result);
// optionally we can start a server too
ads.stopAndCleanup();
} catch (Exception e) {
// Ok, we have something wrong going on ...
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.ldap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
/**
*
*/
public class ApacheDsRule extends ExternalResource {
private final ESLogger logger = Loggers.getLogger(getClass());
private ApacheDsEmbedded ldap;
private final TemporaryFolder temporaryFolder;
public ApacheDsRule(TemporaryFolder temporaryFolder) {
this.temporaryFolder = temporaryFolder;
}
@Override
protected void before() throws Throwable {
ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif", temporaryFolder.newFolder());
ldap.startServer();
}
@Override
protected void after() {
try {
ldap.stopAndCleanup();
} catch (Exception e) {
logger.error("failed to stop and cleanup the embedded ldap server", e);
}
}
public String getUrl() {
return ldap.getUrl();
}
}

View File

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import java.util.List;
@ -17,23 +17,11 @@ import java.util.Map;
import static org.hamcrest.Matchers.*;
public class LdapConnectionTests extends ElasticsearchTestCase {
public static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif", LdapConnectionTests.class.getName());
@BeforeClass
public static void startServer() throws Exception {
ldap.startServer();
}
@AfterClass
public static void stopServer() throws Exception {
ldap.stopAndCleanup();
}
public class LdapConnectionTests extends LdapTest {
@Test
public void testBindWithTemplates() {
String[] ldapUrls = new String[]{ldap.getUrl()};
String[] ldapUrls = new String[]{apacheDsRule.getUrl()};
String groupSearchBase = "o=sevenSeas";
boolean isSubTreeSearch = true;
String[] userTemplates = new String[]{
@ -45,17 +33,18 @@ public class LdapConnectionTests extends ElasticsearchTestCase {
buildLdapSettings(ldapUrls, userTemplates, groupSearchBase, isSubTreeSearch));
String user = "Horatio Hornblower";
char[] userPass = "pass".toCharArray();
SecuredString userPass = SecuredStringTests.build("pass");
LdapConnection ldap = connectionFactory.bind(user, userPass);
Map<String, String[]> attrs = ldap.getUserAttrs(ldap.getAuthenticatedUserDn());
assertThat( attrs, hasKey("uid"));
assertThat(attrs, hasKey("uid"));
assertThat( attrs.get("uid"), arrayContaining("hhornblo"));
}
@Test
@Test(expected = LdapException.class)
public void testBindWithBogusTemplates() {
String[] ldapUrl = new String[]{ldap.getUrl()};
String[] ldapUrl = new String[]{apacheDsRule.getUrl()};
String groupSearchBase = "o=sevenSeas";
boolean isSubTreeSearch = true;
String[] userTemplates = new String[]{
@ -67,15 +56,8 @@ public class LdapConnectionTests extends ElasticsearchTestCase {
buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, isSubTreeSearch));
String user = "Horatio Hornblower";
char[] userPass = "pass".toCharArray();
try {
LdapConnection ldap = ldapFac.bind(user, userPass);
fail("bindWithUserTemplates should have failed");
} catch (LdapException le) {
}
SecuredString userPass = SecuredStringTests.build("pass");
LdapConnection ldap = ldapFac.bind(user, userPass);
}
@Test
@ -85,10 +67,10 @@ public class LdapConnectionTests extends ElasticsearchTestCase {
boolean isSubTreeSearch = true;
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
String user = "Horatio Hornblower";
char[] userPass = "pass".toCharArray();
SecuredString userPass = SecuredStringTests.build("pass");
LdapConnection ldap = ldapFac.bind(user, userPass);
List<String> groups = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
@ -102,26 +84,13 @@ public class LdapConnectionTests extends ElasticsearchTestCase {
String userTemplate = "cn={0},ou=people,o=sevenSeas";
boolean isSubTreeSearch = false;
StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
String user = "Horatio Hornblower";
LdapConnection ldap = ldapFac.bind(user, "pass".toCharArray());
LdapConnection ldap = ldapFac.bind(user, SecuredStringTests.build("pass"));
List<String> groups = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
System.out.println("groups:"+groups);
assertThat(groups, contains("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));
}
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return ImmutableSettings.builder()
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.URLS_SETTING, ldapUrl)
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
}
}

View File

@ -8,13 +8,14 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -23,22 +24,26 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
public class LdapRealmTest extends ElasticsearchTestCase {
static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif", LdapRealmTest.class.getName());
public static String AD_IP = "54.213.145.20";
public static String AD_URL = "ldap://" + AD_IP + ":389";
public class LdapRealmTest extends LdapTest {
public static final String AD_IP = "54.213.145.20";
public static final String AD_URL = "ldap://" + AD_IP + ":389";
public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas";
public static final String VALID_USERNAME = "Thomas Masterman Hardy";
public static final String PASSWORD = "pass";
@BeforeClass
public static void startServer() throws Exception {
ldap.startServer();
private RestController restController;
@Before
public void init() throws Exception {
restController = mock(RestController.class);
}
@AfterClass
public static void stopServer() throws Exception {
ldap.stopAndCleanup();
@Test
public void testRestHeaderRegistration() {
new LdapRealm(ImmutableSettings.EMPTY, mock(LdapConnectionFactory.class), mock(LdapGroupToRoleMapper.class), restController);
verify(restController).registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
@Test
@ -46,11 +51,11 @@ public class LdapRealmTest extends ElasticsearchTestCase {
String groupSearchBase = "o=sevenSeas";
boolean isSubTreeSearch = true;
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch);
Settings settings = LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch);
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(settings);
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
assertThat( user, notNullValue());
assertThat(user.roles(), arrayContaining("HMS Victory"));
}
@ -61,31 +66,30 @@ public class LdapRealmTest extends ElasticsearchTestCase {
boolean isSubTreeSearch = false;
String userTemplate = VALID_USER_TEMPLATE;
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
assertThat( user, notNullValue());
assertThat( user.roles(), arrayContaining("HMS Victory"));
assertThat(user.roles(), arrayContaining("HMS Victory"));
}
@Ignore //this is still failing. not sure why.
@Test
public void testAuthenticate_caching(){
String groupSearchBase = "o=sevenSeas";
boolean isSubTreeSearch = true;
String userTemplate = VALID_USER_TEMPLATE;
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
LdapConnectionTests.buildLdapSettings( ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
LdapConnectionTests.buildLdapSettings( apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
ldapFactory = spy(ldapFactory);
LdapRealm ldap = new LdapRealm( buildCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
LdapRealm ldap = new LdapRealm( buildCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//verify one and only one bind -> caching is working
verify(ldapFactory, times(1)).bind(anyString(), any(char[].class));
verify(ldapFactory, times(1)).bind(anyString(), any(SecuredString.class));
}
@Test
@ -94,15 +98,15 @@ public class LdapRealmTest extends ElasticsearchTestCase {
boolean isSubTreeSearch = true;
String userTemplate = VALID_USER_TEMPLATE;
StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
ldapFactory = spy(ldapFactory);
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//verify two and only two binds -> caching is disabled
verify(ldapFactory, times(2)).bind(anyString(), any(char[].class));
verify(ldapFactory, times(2)).bind(anyString(), any(SecuredString.class));
}
@Ignore
@ -114,9 +118,9 @@ public class LdapRealmTest extends ElasticsearchTestCase {
ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory(
ActiveDirectoryFactoryTests.buildAdSettings(AD_URL, adDomain));
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate( new UsernamePasswordToken("george", "R))Tr0x".toCharArray()));
User user = ldap.authenticate( new UsernamePasswordToken("george", SecuredStringTests.build("R))Tr0x")));
assertThat( user, notNullValue());
assertThat( user.roles(), hasItemInArray("upchuckers"));
@ -128,12 +132,12 @@ public class LdapRealmTest extends ElasticsearchTestCase {
//only set the adDomain, and see if it infers the rest correctly
String adDomain = AD_IP;
Settings settings = ImmutableSettings.builder()
.put(LdapConnectionTests.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomain)
.put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomain)
.build();
ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory( settings );
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
User user = ldap.authenticate( new UsernamePasswordToken("george", "R))Tr0x".toCharArray()));
LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController);
User user = ldap.authenticate( new UsernamePasswordToken("george", SecuredStringTests.build("R))Tr0x")));
assertThat( user, notNullValue());
assertThat( user.roles(), hasItemInArray("upchuckers"));
@ -149,8 +153,8 @@ public class LdapRealmTest extends ElasticsearchTestCase {
private Settings buildCachingSettings() {
return ImmutableSettings.builder()
.put("shield.authc.ldap."+LdapRealm.CACHE_TTL, 1)
.put("shield.authc.ldap."+LdapRealm.CACHE_MAX_USERS, 10)
.put("shield.authc.ldap."+LdapRealm.CACHE_TTL, 100000000)
.put("shield.authc.ldap." + LdapRealm.CACHE_MAX_USERS, 10)
.build();
}
@ -162,6 +166,5 @@ public class LdapRealmTest extends ElasticsearchTestCase {
return new LdapGroupToRoleMapper(settings,
new Environment(settings),
new ResourceWatcherService(settings, new ThreadPool("test")));
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
@Ignore
public abstract class LdapTest extends ElasticsearchTestCase {
static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
private static final TemporaryFolder temporaryFolder = new TemporaryFolder();
protected static final ApacheDsRule apacheDsRule = new ApacheDsRule(temporaryFolder);
@ClassRule
public static final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(apacheDsRule);
static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
}
static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return ImmutableSettings.builder()
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.URLS_SETTING, ldapUrl)
.putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
.put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
}
}

View File

@ -7,15 +7,16 @@ package org.elasticsearch.shield.authc.support;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.User;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class CachingUsernamePasswordRealmTests {
public static class AlwaysAuthenticateCachingRealm extends CachingUsernamePasswordRealm {
public class CachingUsernamePasswordRealmTests extends ElasticsearchTestCase {
static class AlwaysAuthenticateCachingRealm extends CachingUsernamePasswordRealm {
public AlwaysAuthenticateCachingRealm() {
super(ImmutableSettings.EMPTY);
}
@ -28,11 +29,10 @@ public class CachingUsernamePasswordRealmTests {
@Override public String type() { return "test"; }
}
@Test
public void testCache(){
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm();
char[] pass = "pass".toCharArray();
SecuredString pass = SecuredStringTests.build("pass");
realm.authenticate(new UsernamePasswordToken("a", pass));
realm.authenticate(new UsernamePasswordToken("b", pass));
realm.authenticate(new UsernamePasswordToken("c", pass));
@ -50,8 +50,8 @@ public class CachingUsernamePasswordRealmTests {
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm();
String user = "testUser";
char[] pass1 = "pass".toCharArray();
char[] pass2 = "password".toCharArray();
SecuredString pass1 = SecuredStringTests.build("pass");
SecuredString pass2 = SecuredStringTests.build("password");
realm.authenticate(new UsernamePasswordToken(user, pass1));
realm.authenticate(new UsernamePasswordToken(user, pass1));

View File

@ -5,19 +5,18 @@
*/
package org.elasticsearch.shield.authc.support;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
*
*/
public class HasherTests {
public class HasherTests extends ElasticsearchTestCase {
@Test
public void testHtpasswdToolGenerated() throws Exception {
Hasher hasher = Hasher.HTPASSWD;
char[] passwd = "test123".toCharArray();
SecuredString passwd = SecuredStringTests.build("test123");
assertTrue(hasher.verify(passwd, "$2a$05$zxnP0vdREMxnEpkLCDI2OuSaSk/QEKA2.A42iOpI6U2u.RLLOWm1e".toCharArray()));
assertTrue(hasher.verify(passwd, "$2a$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e".toCharArray()));
assertTrue(hasher.verify(passwd, "$apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP.".toCharArray()));
@ -29,7 +28,7 @@ public class HasherTests {
@Test
public void testHtpasswdSelfGenerated() throws Exception {
Hasher hasher = Hasher.HTPASSWD;
char[] passwd = "test123".toCharArray();
SecuredString passwd = SecuredStringTests.build("test123");
assertTrue(hasher.verify(passwd, hasher.hash(passwd)));
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.support;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class SecuredStringTests {
public static SecuredString build(String password){
return new SecuredString(password.toCharArray());
}
@Test
public void testAccessAfterClear(){
SecuredString password = new SecuredString("password".toCharArray());
SecuredString password2 = new SecuredString("password".toCharArray());
password.clear();
try {
password.internalChars();
fail();
} catch(Exception e){}
try {
password.length();
fail();
} catch(Exception e){}
try {
password.charAt(0);
fail();
} catch(Exception e){}
try {
password.concat("_suffix");
fail();
} catch(Exception e){}
assertNotEquals(password, password2);
}
@Test
public void testEqualsHashCode(){
SecuredString password = new SecuredString("password".toCharArray());
SecuredString password2 = new SecuredString("password".toCharArray());
assertEquals(password, password2);
assertEquals(password.hashCode(), password2.hashCode());
}
@Test
public void testsEqualsCharSequence(){
SecuredString password = new SecuredString("password".toCharArray());
StringBuffer password2 = new StringBuffer("password");
String password3 = "password";
assertEquals(password, password2);
assertEquals(password, password3);
}
@Test
public void testConcat() {
SecuredString password = new SecuredString("password".toCharArray());
SecuredString password2 = new SecuredString("password".toCharArray());
SecuredString password3 = password.concat(password2);
assertThat(password3.length(), equalTo(password.length() + password2.length()));
assertThat(password3.internalChars(), equalTo("passwordpassword".toCharArray()));
}
@Test
public void testSubsequence(){
SecuredString password = new SecuredString("password".toCharArray());
SecuredString password2 = password.subSequence(4, 8);
SecuredString password3 = password.subSequence(0, 4);
assertThat(password2.internalChars(), equalTo("word".toCharArray()));
assertThat(password3.internalChars(), equalTo("pass".toCharArray()));
assertThat("ensure original is unmodified", password.internalChars(), equalTo("password".toCharArray()));
}
@Test
public void testUFT8(){
String password = "эластичный поиск-弾性検索";
SecuredString securePass = new SecuredString(password.toCharArray());
byte[] utf8 = securePass.utf8Bytes();
String password2 = new String(utf8, Charsets.UTF_8);
assertThat(password2, equalTo(password));
}
}

View File

@ -31,7 +31,7 @@ public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
@Test
public void testPutToken() throws Exception {
TransportRequest request = new TransportRequest() {};
UsernamePasswordToken.putTokenHeader(request, new UsernamePasswordToken("user1", "test123".toCharArray()));
UsernamePasswordToken.putTokenHeader(request, new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
String header = request.getHeader(UsernamePasswordToken.BASIC_AUTH_HEADER);
assertThat(header, notNullValue());
assertTrue(header.startsWith("Basic "));
@ -53,7 +53,7 @@ public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
UsernamePasswordToken token = UsernamePasswordToken.extractToken(request, null);
assertThat(token, notNullValue());
assertThat(token.principal(), equalTo("user1"));
assertThat(new String(token.credentials()), equalTo("test123"));
assertThat(new String(token.credentials().internalChars()), equalTo("test123"));
}
@Test
@ -87,8 +87,8 @@ public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
@Test
public void testExtractTokenRest() throws Exception {
RestRequest request = mock(RestRequest.class);
UsernamePasswordToken token = new UsernamePasswordToken("username", "changeme".toCharArray());
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(UsernamePasswordToken.basicAuthHeaderValue("username", "changeme".toCharArray()));
UsernamePasswordToken token = new UsernamePasswordToken("username", SecuredStringTests.build("changeme"));
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(UsernamePasswordToken.basicAuthHeaderValue("username", SecuredStringTests.build("changeme")));
assertThat(UsernamePasswordToken.extractToken(request, null), equalTo(token));
}

View File

@ -42,7 +42,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
@Test
public void testParseFile() throws Exception {
Path path = Paths.get(getClass().getResource("roles.yml").toURI());
Map<String, Permission.Global> roles = FileRolesStore.parseFile(path);
Map<String, Permission.Global> roles = FileRolesStore.parseFile(path, logger);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(3));
@ -155,7 +155,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
File file = tempFolder.newFile();
com.google.common.io.Files.write("#".getBytes(Charsets.UTF_8), file);
Map<String, Permission.Global> roles = FileRolesStore.parseFile(file.toPath());
Map<String, Permission.Global> roles = FileRolesStore.parseFile(file.toPath(), logger);
assertThat(roles.keySet(), is(empty()));
}
@ -163,6 +163,6 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
public void testThatInvalidYAMLThrowsElasticsearchException() throws Exception {
File file = tempFolder.newFile();
com.google.common.io.Files.write("user: cluster: ALL indices: '.*': ALL".getBytes(Charsets.UTF_8), file);
FileRolesStore.parseFile(file.toPath());
FileRolesStore.parseFile(file.toPath(), logger);
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.key;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class InternalKeyServiceTests extends ElasticsearchTestCase {
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
private ResourceWatcherService watcherService;
private Settings settings;
private Environment env;
private File keyFile;
@Before
public void init() throws Exception {
keyFile = writeFile("system_key", InternalKeyService.generateKey());
settings = ImmutableSettings.builder()
.put("shield.system_key.file", keyFile.getAbsolutePath())
.put("watcher.interval.high", "2s")
.build();
env = new Environment(settings);
ThreadPool threadPool = new ThreadPool("test");
watcherService = new ResourceWatcherService(settings, threadPool);
watcherService.start();
}
@Test
public void testSigned() throws Exception {
InternalKeyService service = new InternalKeyService(settings, env, watcherService);
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(service.signed(signed), is(true));
}
@Test
public void testSignAndUnsign() throws Exception {
InternalKeyService service = new InternalKeyService(settings, env, watcherService);
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(text.equals(signed), is(false));
String text2 = service.unsignAndVerify(signed);
assertThat(text, equalTo(text2));
}
@Test
public void testSignAndUnsign_NoKeyFile() throws Exception {
InternalKeyService service = new InternalKeyService(ImmutableSettings.EMPTY, env, watcherService);
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
assertThat(text, equalTo(signed));
text = service.unsignAndVerify(signed);
assertThat(text, equalTo(signed));
}
@Test
public void testReloadKey() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
InternalKeyService service = new InternalKeyService(settings, env, watcherService, new InternalKeyService.Listener() {
@Override
public void onKeyRefresh() {
latch.countDown();
}
});
String text = randomAsciiOfLength(10);
String signed = service.sign(text);
// we need to sleep so to ensure the timestamp of the file will definitely change
// and so the resource watcher will pick up the change.
sleep(1000);
Streams.copy(InternalKeyService.generateKey(), keyFile);
if (!latch.await(10, TimeUnit.SECONDS)) {
fail("waiting too long for test to complete. Expected callback is not called");
}
String signed2 = service.sign(text);
assertThat(signed.equals(signed2), is(false));
}
private File writeFile(String name, byte[] content) throws IOException {
File file = tempFolder.newFile(name);
Streams.copy(content, file);
return file;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.key.tool;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.key.InternalKeyService;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.elasticsearch.shield.key.tool.SystemKeyTool.Generate;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class SystemKeyToolTests extends CliToolTestCase {
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
private Terminal terminal;
private Environment env;
@Before
public void init() throws Exception {
terminal = mock(Terminal.class);
env = mock(Environment.class);
}
@Test
public void testParse_NoArgs() throws Exception {
CliTool.Command cmd = new SystemKeyTool().parse("generate", args(""));
assertThat(cmd, instanceOf(Generate.class));
Generate generate = (Generate) cmd;
assertThat(generate.path, nullValue());
}
@Test
public void testParse_FileArg() throws Exception {
Path path = tempFolder.newFile().toPath();
CliTool.Command cmd = new SystemKeyTool().parse("generate", args(path.toAbsolutePath().toString()));
assertThat(cmd, instanceOf(Generate.class));
Generate generate = (Generate) cmd;
assertThat(Files.isSameFile(generate.path, path), is(true));
}
@Test
public void testGenerate() throws Exception {
Path path = tempFolder.newFile().toPath();
Generate generate = new Generate(terminal, path);
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalKeyService.KEY_SIZE / 8));
}
@Test
public void testGenerate_PathInSettings() throws Exception {
Path path = tempFolder.newFile().toPath();
Settings settings = ImmutableSettings.builder()
.put("shield.system_key.file", path.toAbsolutePath().toString())
.build();
Generate generate = new Generate(terminal, null);
CliTool.ExitStatus status = generate.execute(settings, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalKeyService.KEY_SIZE / 8));
}
@Test
public void testGenerate_DefaultPath() throws Exception {
File config = tempFolder.newFolder();
File shieldConfig = new File(config, ShieldPlugin.NAME);
shieldConfig.mkdirs();
Path path = new File(shieldConfig, ".system_key").toPath();
when(env.configFile()).thenReturn(config);
Generate generate = new Generate(terminal, null);
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
assertThat(status, is(CliTool.ExitStatus.OK));
byte[] bytes = Streams.copyToByteArray(path.toFile());
assertThat(bytes.length, is(InternalKeyService.KEY_SIZE / 8));
}
}

View File

@ -22,7 +22,7 @@ public class ShieldPluginTests extends ShieldIntegrationTest {
logger.info("--> Checking nodes info that shield plugin is loaded");
for (NodeInfo nodeInfo : nodeInfos.getNodes()) {
assertThat(nodeInfo.getPlugins().getInfos(), hasSize(1));
assertThat(nodeInfo.getPlugins().getInfos().get(0).getName(), is(SecurityPlugin.NAME));
assertThat(nodeInfo.getPlugins().getInfos().get(0).getName(), is(ShieldPlugin.NAME));
}
}

View File

@ -6,17 +6,17 @@
package org.elasticsearch.shield.test;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.SecurityException;
import org.elasticsearch.shield.ShieldException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class ShieldAssertions {
public static void assertContainsWWWAuthenticateHeader(org.elasticsearch.shield.SecurityException e) {
public static void assertContainsWWWAuthenticateHeader(ShieldException e) {
assertThat(e.status(), is(RestStatus.UNAUTHORIZED));
assertThat(e.getHeaders(), hasKey("WWW-Authenticate"));
assertThat(e.getHeaders().get("WWW-Authenticate"), hasSize(1));
assertThat(e.getHeaders().get("WWW-Authenticate").get(0), is(SecurityException.HEADERS.get("WWW-Authenticate").get(0)));
assertThat(e.getHeaders().get("WWW-Authenticate").get(0), is(ShieldException.HEADERS.get("WWW-Authenticate").get(0)));
}
}

View File

@ -9,13 +9,15 @@ import com.google.common.base.Charsets;
import com.google.common.net.InetAddresses;
import org.apache.lucene.util.AbstractRandomizedTest;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.os.OsUtils;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.transport.Transport;
@ -61,11 +63,11 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
File folder = newFolder();
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray()))
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put("discovery.zen.ping.multicast.enabled", false)
.put("discovery.type", "zen")
.put("node.mode", "network")
.put("plugin.types", SecurityPlugin.class.getName())
.put("plugin.types", ShieldPlugin.class.getName())
.put("shield.authc.esusers.files.users", writeFile(folder, "users", CONFIG_STANDARD_USER))
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES))
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", CONFIG_ROLE_ALLOW_ALL))
@ -84,7 +86,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
@Override
protected Settings transportClientSettings() {
return ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray()))
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
.put("node.mode", "network")
@ -93,9 +95,13 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
}
protected String writeFile(File folder, String name, String content) {
return writeFile(folder, name, content.getBytes(Charsets.UTF_8));
}
protected String writeFile(File folder, String name, byte[] content) {
Path file = folder.toPath().resolve(name);
try {
com.google.common.io.Files.write(content.getBytes(Charsets.UTF_8), file.toFile());
Streams.copy(content, file.toFile());
} catch (IOException e) {
throw new ElasticsearchException("Error writing file in test", e);
}
@ -113,8 +119,8 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
return DEFAULT_USER_NAME;
}
protected String getClientPassword() {
return DEFAULT_PASSWORD;
protected SecuredString getClientPassword() {
return new SecuredString(DEFAULT_PASSWORD.toCharArray());
}
protected Settings getSSLSettingsForStore(String resourcePathToStore, String password) {

View File

@ -11,6 +11,7 @@ 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.node.internal.InternalNode;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
import org.elasticsearch.transport.Transport;
import org.junit.Test;
@ -37,6 +38,7 @@ public class IpFilteringIntegrationTests extends ShieldIntegrationTest {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
ImmutableSettings.Builder builder = settingsBuilder().put(super.nodeSettings(nodeOrdinal));
builder.put(InternalNode.HTTP_ENABLED, true);
// either deny all or do not have a configuration file, as this denies by default
if (getRandom().nextBoolean()) {
File folder = newFolder();
@ -59,7 +61,7 @@ public class IpFilteringIntegrationTests extends ShieldIntegrationTest {
@Test(expected = SocketException.class)
public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaTransportClient() throws Exception {
TransportAddress transportAddress = (InetSocketTransportAddress) internalCluster().getDataNodeInstance(Transport.class).boundAddress().boundAddress();
TransportAddress transportAddress = internalCluster().getDataNodeInstance(Transport.class).boundAddress().boundAddress();
assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class)));
InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress;
trySocketConnection(inetSocketTransportAddress.address());

View File

@ -13,12 +13,14 @@ import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.net.InetAddresses;
import org.elasticsearch.common.settings.ImmutableSettings;
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.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
@ -40,6 +42,12 @@ import static org.hamcrest.Matchers.*;
public class SslIntegrationTests extends ShieldIntegrationTest {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.builder().put(super.nodeSettings(nodeOrdinal))
.put(InternalNode.HTTP_ENABLED, true).build();
}
@Test
public void testThatInternallyCreatedTransportClientCanConnect() throws Exception {
Client transportClient = internalCluster().transportClient();
@ -89,7 +97,7 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
.put("name", "programmatic_node")
.put("cluster.name", internalCluster().getClusterName())
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray()))
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
@ -114,7 +122,7 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
.put("discovery.type", "zen")
.putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress())
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray()))
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
.put("shield.transport.n2n.ip_filter.file", writeFile(newFolder(), "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL))
@ -165,7 +173,7 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
String url = String.format(Locale.ROOT, "https://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort());
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestProperty("Authorization", UsernamePasswordToken.basicAuthHeaderValue(DEFAULT_USER_NAME, DEFAULT_PASSWORD.toCharArray()));
connection.setRequestProperty("Authorization", UsernamePasswordToken.basicAuthHeaderValue(getClientUsername(), getClientPassword()));
connection.connect();
assertThat(connection.getResponseCode(), is(200));

View File

@ -13,6 +13,7 @@ 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.node.internal.InternalNode;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
import org.junit.Test;
@ -49,6 +50,7 @@ public class SslRequireAuthTests extends ShieldIntegrationTest {
protected Settings nodeSettings(int nodeOrdinal) {
return settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(InternalNode.HTTP_ENABLED, true)
.put(getSSLSettingsForStore("certs/simple/testnode.jks", "testnode"))
.put("shield.transport.ssl.require.client.auth", true)
.put("shield.http.ssl.require.client.auth", true)
@ -128,7 +130,7 @@ public class SslRequireAuthTests extends ShieldIntegrationTest {
String url = String.format(Locale.ROOT, "https://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort());
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestProperty("Authorization", UsernamePasswordToken.basicAuthHeaderValue(DEFAULT_USER_NAME, DEFAULT_PASSWORD.toCharArray()));
connection.setRequestProperty("Authorization", UsernamePasswordToken.basicAuthHeaderValue(getClientUsername(), getClientPassword()));
connection.connect();
assertThat(connection.getResponseCode(), is(200));

View File

@ -0,0 +1,184 @@
/*
* 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.test;
import com.carrotsearch.randomizedtesting.SysGlobals;
import com.carrotsearch.randomizedtesting.annotations.Name;
import com.google.common.base.Charsets;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.os.OsUtils;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.key.InternalKeyService;
import org.elasticsearch.shield.plugin.ShieldPlugin;
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
import org.elasticsearch.test.rest.ElasticsearchRestTests;
import org.elasticsearch.test.rest.RestTestCandidate;
import org.elasticsearch.transport.TransportModule;
import org.junit.AfterClass;
import org.junit.ClassRule;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class ShieldRestTests extends ElasticsearchRestTests {
public static final int CHILD_JVM_ID = Integer.parseInt(System.getProperty(SysGlobals.CHILDVM_SYSPROP_JVM_ID, "0"));
public static final int BASE_PORT = 33000 + CHILD_JVM_ID * 100;
public static final String BASE_PORT_RANGE = BASE_PORT + "-" + (BASE_PORT+10) ;
protected static final boolean ENABLE_TRANSPORT_SSL = true;
protected static final boolean SHIELD_AUDIT_ENABLED = false;
protected static final String DEFAULT_USER_NAME = "test_user";
protected static final String DEFAULT_PASSWORD = "changeme";
protected static final String DEFAULT_ROLE = "user";
public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n";
public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_USER_NAME + ":" + DEFAULT_ROLE + "\n";
public static final String CONFIG_ROLE_ALLOW_ALL = "user:\n" +
" cluster: ALL\n" +
" indices:\n" +
" '.*': ALL\n";
static {
final byte[] key;
try {
key = InternalKeyService.generateKey();
} catch (Exception e) {
throw new RuntimeException(e);
}
InternalTestCluster.DEFAULT_SETTINGS_SOURCE = new SettingsSource() {
@Override
public Settings node(int nodeOrdinal) {
File store;
try {
store = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());
assertThat(store.exists(), is(true));
} catch (Exception e) {
throw new ElasticsearchException("Error reading test node cert", e);
}
String password = "testnode";
File folder = createFolder();
String keyFile = writeFile(folder, "system_key", key);
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put(InternalKeyService.FILE_SETTING, keyFile)
.put("request.headers.Authorization", basicAuthHeaderValue(DEFAULT_USER_NAME, SecuredStringTests.build(DEFAULT_PASSWORD)))
.put("discovery.zen.ping.multicast.enabled", false)
.put("discovery.type", "zen")
.put("node.mode", "network")
.put("plugin.types", ShieldPlugin.class.getName())
.put("shield.authc.esusers.files.users", createFile(folder, "users", CONFIG_STANDARD_USER))
.put("shield.authc.esusers.files.users_roles", createFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES))
.put("shield.authz.store.files.roles", createFile(folder, "roles.yml", CONFIG_ROLE_ALLOW_ALL))
.put("shield.transport.n2n.ip_filter.file", createFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL))
.put("shield.transport.ssl", ENABLE_TRANSPORT_SSL)
.put("shield.transport.ssl.keystore", store.getPath())
.put("shield.transport.ssl.keystore_password", password)
.put("shield.transport.ssl.truststore", store.getPath())
.put("shield.transport.ssl.truststore_password", password)
.put("shield.http.ssl", false)
.put("transport.tcp.port", BASE_PORT_RANGE)
.putArray("discovery.zen.ping.unicast.hosts", "127.0.0.1:" + BASE_PORT, "127.0.0.1:" + (BASE_PORT + 1), "127.0.0.1:" + (BASE_PORT + 2), "127.0.0.1:" + (BASE_PORT + 3))
.put("shield.audit.enabled", SHIELD_AUDIT_ENABLED);
builder.put("network.host", "127.0.0.1");
if (OsUtils.MAC) {
builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1");
}
return builder.build();
}
@Override
public Settings transportClient() {
File store;
String password = "testclient";
try {
store = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks").toURI());
assertThat(store.exists(), is(true));
} catch (Exception e) {
throw new ElasticsearchException("Error reading test client cert", e);
}
File folder = createFolder();
return ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(DEFAULT_USER_NAME, SecuredStringTests.build(DEFAULT_PASSWORD)))
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
.put("node.mode", "network")
.put("shield.transport.n2n.ip_filter.file", createFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL))
.put("shield.transport.ssl", ENABLE_TRANSPORT_SSL)
.put("shield.transport.ssl.keystore", store.getPath())
.put("shield.transport.ssl.keystore_password", password)
.put("shield.transport.ssl.truststore", store.getPath())
.put("shield.transport.ssl.truststore_password", password)
.put("cluster.name", internalCluster().getClusterName())
.build();
}
};
}
@ClassRule
public static TemporaryFolder tmpFolder = new TemporaryFolder();
@AfterClass
public static void cleanup() {
tmpFolder = null;
}
public ShieldRestTests(@Name("yaml") RestTestCandidate testCandidate) {
super(testCandidate);
}
@Override
protected Settings restClientSettings() {
return ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(DEFAULT_USER_NAME, SecuredStringTests.build(DEFAULT_PASSWORD))).build();
}
/* static helper methods for the global test class */
static File createFolder() {
try {
return tmpFolder.newFolder();
} catch (IOException ioe) {
fail("could not create temporary folder");
return null;
}
}
static String createFile(File folder, String name, String content) {
return writeFile(folder, name, content.getBytes(Charsets.UTF_8));
}
static String writeFile(File folder, String name, byte[] content) {
Path file = folder.toPath().resolve(name);
try {
Streams.copy(content, file.toFile());
} catch (IOException e) {
throw new ElasticsearchException("Error writing file in test", e);
}
return file.toFile().getAbsolutePath();
}
}

View File

@ -25,6 +25,7 @@ indices:admin/cache/clear
indices:admin/close
indices:admin/create
indices:admin/delete
indices:admin/get
indices:admin/exists
indices:admin/flush
indices:admin/mapping/delete