Conflicts:
	docs/public/06-ldap.asciidoc
	docs/public/08-auditing.asciidoc
	docs/structured/01-introduction.asciidoc
	docs/structured/02-architecture.asciidoc

Original commit: elastic/x-pack-elasticsearch@ddf1f0d910
This commit is contained in:
Paul Echeverri 2014-10-22 16:34:16 -07:00
commit a6ba0eec2d
9 changed files with 371 additions and 70 deletions

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.shield.audit.logfile; package org.elasticsearch.shield.audit.logfile;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
@ -41,69 +43,126 @@ public class LoggingAuditTrail implements AuditTrail {
@Override @Override
public void anonymousAccess(String action, TransportMessage<?> message) { public void anonymousAccess(String action, TransportMessage<?> message) {
String indices = indices(message);
if (indices != null) {
if (logger.isDebugEnabled()) {
logger.debug("ANONYMOUS_ACCESS\thost=[{}], action=[{}], indices=[{}], request=[{}]", message.remoteAddress(), action, indices, message);
} else {
logger.warn("ANONYMOUS_ACCESS\thost=[{}], action=[{}], indices=[{}]", message.remoteAddress(), action, indices);
}
} else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ANONYMOUS_ACCESS\thost=[{}], action=[{}], request=[{}]", message.remoteAddress(), action, message); logger.debug("ANONYMOUS_ACCESS\thost=[{}], action=[{}], request=[{}]", message.remoteAddress(), action, message);
} else { } else {
logger.warn("ANONYMOUS_ACCESS\thost=[{}], action=[{}]", message.remoteAddress(), action); logger.warn("ANONYMOUS_ACCESS\thost=[{}], action=[{}]", message.remoteAddress(), action);
} }
} }
}
@Override @Override
public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) { public void authenticationFailed(AuthenticationToken token, String action, TransportMessage<?> message) {
String indices = indices(message);
if (indices != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("AUTHENTICATION_FAILED\thost=[{}], action=[{}], principal=[{}], request=[{}]", message.remoteAddress(), action, token.principal(), message); logger.debug("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], action=[{}], indices=[{}], request=[{}]", message.remoteAddress(), token.principal(), action, indices, message);
} else { } else {
logger.error("AUTHENTICATION_FAILED\thost=[{}], action=[{}], principal=[{}]", message.remoteAddress(), action, token.principal()); logger.error("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], action=[{}], indices=[{}]", message.remoteAddress(), token.principal(), action, indices);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], action=[{}], request=[{}]", message.remoteAddress(), token.principal(), action, message);
} else {
logger.error("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], action=[{}]", message.remoteAddress(), token.principal(), action);
}
} }
} }
@Override @Override
public void authenticationFailed(AuthenticationToken token, RestRequest request) { public void authenticationFailed(AuthenticationToken token, RestRequest request) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("AUTHENTICATION_FAILED\thost=[{}], URI=[{}], principal=[{}], request=[{}]", request.getRemoteAddress(), request.uri(), token.principal(), request); logger.debug("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], URI=[{}], request=[{}]", request.getRemoteAddress(), token.principal(), request.uri(), request);
} else { } else {
logger.error("AUTHENTICATION_FAILED\thost=[{}], URI=[{}], principal=[{}]", request.getRemoteAddress(), request.uri(), token.principal()); logger.error("AUTHENTICATION_FAILED\thost=[{}], principal=[{}], URI=[{}]", request.getRemoteAddress(), token.principal(), request.uri());
} }
} }
@Override @Override
public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message) { public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage<?> message) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], action=[{}], principal=[{}], request=[{}]", realm, message.remoteAddress(), action, token.principal(), message); String indices = indices(message);
if (indices != null) {
logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], principal=[{}], action=[{}], indices=[{}], request=[{}]", realm, message.remoteAddress(), token.principal(), action, indices, message);
} else {
logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], principal=[{}], action=[{}], request=[{}]", realm, message.remoteAddress(), token.principal(), action, message);
}
} }
} }
@Override @Override
public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) { public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], URI=[{}], principal=[{}], request=[{}]", realm, request.getRemoteAddress(), request.uri(), token.principal(), request); logger.trace("AUTHENTICATION_FAILED[{}]\thost=[{}], principal=[{}], URI=[{}], request=[{}]", realm, request.getRemoteAddress(), token.principal(), request.uri(), request);
} }
} }
@Override @Override
public void accessGranted(User user, String action, TransportMessage<?> message) { public void accessGranted(User user, String action, TransportMessage<?> message) {
String indices = indices(message);
if (indices != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ACCESS_GRANTED\thost=[{}], action=[{}], principal=[{}], request=[{}]", message.remoteAddress(), action, user.principal(), message); logger.debug("ACCESS_GRANTED\thost=[{}], principal=[{}], action=[{}], indices=[{}], request=[{}]", message.remoteAddress(), user.principal(), action, indices, message);
} else { } else {
logger.info("ACCESS_GRANTED\thost=[{}], action=[{}], principal=[{}]", message.remoteAddress(), action, user.principal()); logger.info("ACCESS_GRANTED\thost=[{}], principal=[{}], action=[{}], indices=[{}]", message.remoteAddress(), user.principal(), action, indices);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("ACCESS_GRANTED\thost=[{}], principal=[{}], action=[{}], request=[{}]", message.remoteAddress(), user.principal(), action, message);
} else {
logger.info("ACCESS_GRANTED\thost=[{}], principal=[{}], action=[{}]", message.remoteAddress(), user.principal(), action);
}
} }
} }
@Override @Override
public void accessDenied(User user, String action, TransportMessage<?> message) { public void accessDenied(User user, String action, TransportMessage<?> message) {
String indices = indices(message);
if (indices != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ACCESS_DENIED\thost=[{}], action=[{}], principal=[{}], request=[{}]", message.remoteAddress(), action, user.principal(), message); logger.debug("ACCESS_DENIED\thost=[{}], principal=[{}], action=[{}], indices=[{}], request=[{}]", message.remoteAddress(), user.principal(), action, indices, message);
} else { } else {
logger.error("ACCESS_DENIED\thost=[{}], action=[{}], principal=[{}]", message.remoteAddress(), action, user.principal()); logger.error("ACCESS_DENIED\thost=[{}], principal=[{}], action=[{}], indices=[{}]", message.remoteAddress(), user.principal(), action, indices);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("ACCESS_DENIED\thost=[{}], principal=[{}], action=[{}], request=[{}]", message.remoteAddress(), user.principal(), action, message);
} else {
logger.error("ACCESS_DENIED\thost=[{}], principal=[{}], action=[{}]", message.remoteAddress(), user.principal(), action);
}
} }
} }
@Override @Override
public void tamperedRequest(User user, String action, TransportRequest request) { public void tamperedRequest(User user, String action, TransportRequest request) {
String indices = indices(request);
if (indices != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("TAMPERED REQUEST\thost=[{}], action=[{}], principal=[{}], request=[{}]", request.remoteAddress(), action, user.principal(), request); logger.debug("TAMPERED REQUEST\thost=[{}], principal=[{}], action=[{}], indices=[{}], request=[{}]", request.remoteAddress(), user.principal(), action, indices, request);
} else { } else {
logger.error("TAMPERED REQUEST\thost=[{}], action=[{}], principal=[{}]", request.remoteAddress(), action, user.principal()); logger.error("TAMPERED REQUEST\thost=[{}], principal=[{}], action=[{}], indices=[{}]", request.remoteAddress(), user.principal(), action, indices);
} }
} else {
if (logger.isDebugEnabled()) {
logger.debug("TAMPERED REQUEST\thost=[{}], principal=[{}], action=[{}], request=[{}]", request.remoteAddress(), user.principal(), action, request);
} else {
logger.error("TAMPERED REQUEST\thost=[{}], principal=[{}], action=[{}]", request.remoteAddress(), user.principal(), action);
}
}
}
private static String indices(TransportMessage message) {
if (message instanceof IndicesRequest) {
return Strings.arrayToCommaDelimitedString(((IndicesRequest) message).indices());
}
return null;
} }
} }

View File

@ -32,7 +32,6 @@ import java.util.Hashtable;
public class ActiveDirectoryConnectionFactory extends AbstractComponent implements LdapConnectionFactory { public class ActiveDirectoryConnectionFactory extends AbstractComponent implements LdapConnectionFactory {
public static final String AD_DOMAIN_NAME_SETTING = "domain_name"; public static final String AD_DOMAIN_NAME_SETTING = "domain_name";
public static final String AD_PORT = "default_port";
public static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search_dn"; public static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search_dn";
static final String MODE_NAME = "active_directory"; static final String MODE_NAME = "active_directory";
@ -48,9 +47,8 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen
throw new ShieldException("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)); userSearchDN = componentSettings.get(AD_USER_SEARCH_BASEDN_SETTING, buildDnFromDomain(domainName));
int port = componentSettings.getAsInt(AD_PORT, 636);
String protocol = port == 389 ? "ldap://" : "ldaps://"; String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING, new String[] { "ldaps://" + domainName + ":636"});
String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING, new String[] { protocol + domainName + ":" + port });
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder() ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder()
.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") .put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")

View File

@ -20,7 +20,7 @@ import org.elasticsearch.shield.authc.support.SecuredString;
*/ */
public interface LdapConnectionFactory { public interface LdapConnectionFactory {
static final String URLS_SETTING = "urls"; //comma separated static final String URLS_SETTING = "url"; //comma separated
/** /**
* Password authenticated bind * Password authenticated bind

View File

@ -5,10 +5,12 @@
*/ */
package org.elasticsearch.shield.authc.ldap; package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap; import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.transport.ssl.SSLTrustConfig; import org.elasticsearch.shield.transport.ssl.SSLTrustConfig;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@ -28,6 +30,7 @@ import java.util.Locale;
*/ */
public class LdapSslSocketFactory extends SocketFactory { public class LdapSslSocketFactory extends SocketFactory {
static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket";
private static ESLogger logger = ESLoggerFactory.getLogger(LdapSslSocketFactory.class.getName()); private static ESLogger logger = ESLoggerFactory.getLogger(LdapSslSocketFactory.class.getName());
private static LdapSslSocketFactory instance; private static LdapSslSocketFactory instance;
@ -51,7 +54,7 @@ public class LdapSslSocketFactory extends SocketFactory {
/** /**
* This is invoked by JNDI and the returned SocketFactory must be an LdapSslSocketFactory object * This is invoked by JNDI and the returned SocketFactory must be an LdapSslSocketFactory object
* @return * @return a singleton instance of LdapSslSocketFactory set by calling the init static method.
*/ */
public static SocketFactory getDefault() { public static SocketFactory getDefault() {
assert instance != null; assert instance != null;
@ -92,19 +95,21 @@ public class LdapSslSocketFactory extends SocketFactory {
/** /**
* If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder * If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder
* @param ldapUrls * @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing)
* @param builder set of jndi properties, that will * @param builder set of jndi properties, that will
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.
*/ */
public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) { public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) {
boolean needsSSL = false; boolean secureProtocol = ldapUrls[0].toLowerCase(Locale.getDefault()).startsWith("ldaps://");
for(String url: ldapUrls){ for(String url: ldapUrls){
if (url.toLowerCase(Locale.getDefault()).startsWith("ldaps://")) { if (secureProtocol != url.toLowerCase(Locale.getDefault()).startsWith("ldaps://")) {
needsSSL = true; //this is because LdapSSLSocketFactory produces only SSL sockets and not clear text sockets
break; throw new ShieldSettingsException("Configured ldap protocols are not all equal " +
"(ldaps://.. and ldap://..): [" + Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
} }
} }
if (needsSSL && instance != null) { if (secureProtocol && instance != null) {
builder.put("java.naming.ldap.factory.socket", LdapSslSocketFactory.class.getName()); builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
} else { } else {
logger.warn("LdapSslSocketFactory not used for LDAP connections"); logger.warn("LdapSslSocketFactory not used for LDAP connections");
} }

View File

@ -9,7 +9,6 @@ import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.base.Predicate; import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.cache.CacheLoader; import org.elasticsearch.common.cache.CacheLoader;
import org.elasticsearch.common.cache.LoadingCache; import org.elasticsearch.common.cache.LoadingCache;
@ -233,13 +232,26 @@ public interface Permission {
} }
} }
for (Group group : groups) { if (indices == null) {
if (group.check(action, indices)) {
return true; return true;
} }
// for every index, at least one group should match it... otherwise denied
for (String index : indices) {
boolean grant = false;
for (Group group : groups) {
if (group.check(action, index)) {
grant = true;
break;
} }
}
if (!grant) {
return false; return false;
} }
}
return true;
}
public static class Group { public static class Group {
@ -264,21 +276,8 @@ public interface Permission {
return indices; return indices;
} }
public boolean check(String action, Set<String> indices) { public boolean check(String action, String index) {
return actionMatcher.apply(action) && !(index != null && !indexNameMatcher.apply(index));
if (!actionMatcher.apply(action)) {
return false;
}
if (indices != null) {
for (String index : indices) {
if (!indexNameMatcher.apply(index)) {
return false;
}
}
}
return true;
} }

View File

@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.indicesQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.*;
/**
*
*/
public class MultipleIndicesPermissionsTests extends ShieldIntegrationTest {
public static final String ROLES = "user:\n" +
" cluster: all\n" +
" indices:\n" +
" '.*': manage\n" +
" '.*': write\n" +
" 'test': read\n" +
" 'test1': read\n";
@Override
protected String configRole() {
return ROLES;
}
@Test
public void testDifferetCombinationsOfIndices() throws Exception {
IndexResponse indexResponse = index("test", "type", jsonBuilder()
.startObject()
.field("name", "value")
.endObject());
assertThat(indexResponse.isCreated(), is(true));
indexResponse = index("test1", "type", jsonBuilder()
.startObject()
.field("name", "value1")
.endObject());
assertThat(indexResponse.isCreated(), is(true));
refresh();
Client client = internalCluster().transportClient();
// no specifying an index, should replace indices with the permitted ones (test & test1)
SearchResponse searchResponse = client.prepareSearch().setQuery(matchAllQuery()).get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2);
searchResponse = client.prepareSearch().setQuery(indicesQuery(matchAllQuery(), "test1")).get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2);
// _all should expand to all the permitted indices
searchResponse = client.prepareSearch("_all").setQuery(matchAllQuery()).get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2);
// wildcards should expand to all the permitted indices
searchResponse = client.prepareSearch("test*").setQuery(matchAllQuery()).get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2);
// specifying a permitted index, should only return results from that index
searchResponse = client.prepareSearch("test1").setQuery(indicesQuery(matchAllQuery(), "test1")).get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 1);
// specifying a forbidden index, should throw an authorization exception
try {
client.prepareSearch("test2").setQuery(indicesQuery(matchAllQuery(), "test1")).get();
fail("expected an authorization exception when searching a forbidden index");
} catch (AuthorizationException ae) {
// expected
}
try {
client.prepareSearch("test", "test2").setQuery(matchAllQuery()).get();
fail("expected an authorization exception when one of mulitple indices is forbidden");
} catch (AuthorizationException ae) {
// expected
}
}
}

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.shield.audit.logfile; package org.elasticsearch.shield.audit.logfile;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User; import org.elasticsearch.shield.User;
@ -32,37 +34,55 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
for (Level level : Level.values()) { for (Level level : Level.values()) {
CapturingLogger logger = new CapturingLogger(level); CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger); LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.anonymousAccess("_action", new MockMessage()); TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
auditTrail.anonymousAccess("_action", message);
switch (level) { switch (level) {
case ERROR: case ERROR:
assertEmptyLog(logger); assertEmptyLog(logger);
break; break;
case WARN: case WARN:
case INFO: case INFO:
if (message instanceof IndicesRequest) {
assertMsg(logger, Level.WARN, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action], indices=[idx1,idx2]");
} else {
assertMsg(logger, Level.WARN, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action]"); assertMsg(logger, Level.WARN, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action]");
}
break; break;
case DEBUG: case DEBUG:
case TRACE: case TRACE:
if (message instanceof IndicesRequest) {
assertMsg(logger, Level.DEBUG, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action], indices=[idx1,idx2], request=[mock-message]");
} else {
assertMsg(logger, Level.DEBUG, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action], request=[mock-message]"); assertMsg(logger, Level.DEBUG, "ANONYMOUS_ACCESS\thost=[local[_host]], action=[_action], request=[mock-message]");
} }
} }
} }
}
@Test @Test
public void testAuthenticationFailed() throws Exception { public void testAuthenticationFailed() throws Exception {
for (Level level : Level.values()) { for (Level level : Level.values()) {
CapturingLogger logger = new CapturingLogger(level); CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger); LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.authenticationFailed(new MockToken(), "_action", new MockMessage()); TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
auditTrail.authenticationFailed(new MockToken(), "_action", message);
switch (level) { switch (level) {
case ERROR: case ERROR:
case WARN: case WARN:
case INFO: case INFO:
assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[local[_host]], action=[_action], principal=[_principal]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[local[_host]], principal=[_principal], action=[_action], indices=[idx1,idx2]");
} else {
assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[local[_host]], principal=[_principal], action=[_action]");
}
break; break;
case DEBUG: case DEBUG:
case TRACE: case TRACE:
assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[local[_host]], action=[_action], principal=[_principal], request=[mock-message]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[local[_host]], principal=[_principal], action=[_action], indices=[idx1,idx2], request=[mock-message]");
} else {
assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[local[_host]], principal=[_principal], action=[_action], request=[mock-message]");
}
} }
} }
} }
@ -81,11 +101,11 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
case ERROR: case ERROR:
case WARN: case WARN:
case INFO: case INFO:
assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[_hostname:9200], URI=[_uri], principal=[_principal]"); assertMsg(logger, Level.ERROR, "AUTHENTICATION_FAILED\thost=[_hostname:9200], principal=[_principal], URI=[_uri]");
break; break;
case DEBUG: case DEBUG:
case TRACE: case TRACE:
assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[_hostname:9200], URI=[_uri], principal=[_principal], request=[rest_request]"); assertMsg(logger, Level.DEBUG, "AUTHENTICATION_FAILED\thost=[_hostname:9200], principal=[_principal], URI=[_uri], request=[rest_request]");
} }
} }
} }
@ -95,7 +115,8 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
for (Level level : Level.values()) { for (Level level : Level.values()) {
CapturingLogger logger = new CapturingLogger(level); CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger); LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.authenticationFailed("_realm", new MockToken(), "_action", new MockMessage()); TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
auditTrail.authenticationFailed("_realm", new MockToken(), "_action", message);
switch (level) { switch (level) {
case ERROR: case ERROR:
case WARN: case WARN:
@ -104,7 +125,11 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
assertEmptyLog(logger); assertEmptyLog(logger);
break; break;
case TRACE: case TRACE:
assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[local[_host]], action=[_action], principal=[_principal], request=[mock-message]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[local[_host]], principal=[_principal], action=[_action], indices=[idx1,idx2], request=[mock-message]");
} else {
assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[local[_host]], principal=[_principal], action=[_action], request=[mock-message]");
}
} }
} }
} }
@ -127,7 +152,7 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
assertEmptyLog(logger); assertEmptyLog(logger);
break; break;
case TRACE: case TRACE:
assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[_hostname:9200], URI=[_uri], principal=[_principal], request=[rest_request]"); assertMsg(logger, Level.TRACE, "AUTHENTICATION_FAILED[_realm]\thost=[_hostname:9200], principal=[_principal], URI=[_uri], request=[rest_request]");
} }
} }
} }
@ -137,18 +162,27 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
for (Level level : Level.values()) { for (Level level : Level.values()) {
CapturingLogger logger = new CapturingLogger(level); CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger); LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.accessGranted(new User.Simple("_username", "r1"), "_action", new MockMessage()); TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
auditTrail.accessGranted(new User.Simple("_username", "r1"), "_action", message);
switch (level) { switch (level) {
case ERROR: case ERROR:
case WARN: case WARN:
assertEmptyLog(logger); assertEmptyLog(logger);
break; break;
case INFO: case INFO:
assertMsg(logger, Level.INFO, "ACCESS_GRANTED\thost=[local[_host]], action=[_action], principal=[_username]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.INFO, "ACCESS_GRANTED\thost=[local[_host]], principal=[_username], action=[_action], indices=[idx1,idx2]");
} else {
assertMsg(logger, Level.INFO, "ACCESS_GRANTED\thost=[local[_host]], principal=[_username], action=[_action]");
}
break; break;
case DEBUG: case DEBUG:
case TRACE: case TRACE:
assertMsg(logger, Level.DEBUG, "ACCESS_GRANTED\thost=[local[_host]], action=[_action], principal=[_username], request=[mock-message]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.DEBUG, "ACCESS_GRANTED\thost=[local[_host]], principal=[_username], action=[_action], indices=[idx1,idx2], request=[mock-message]");
} else {
assertMsg(logger, Level.DEBUG, "ACCESS_GRANTED\thost=[local[_host]], principal=[_username], action=[_action], request=[mock-message]");
}
} }
} }
} }
@ -158,16 +192,25 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
for (Level level : Level.values()) { for (Level level : Level.values()) {
CapturingLogger logger = new CapturingLogger(level); CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger); LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
auditTrail.accessDenied(new User.Simple("_username", "r1"), "_action", new MockMessage()); TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
auditTrail.accessDenied(new User.Simple("_username", "r1"), "_action", message);
switch (level) { switch (level) {
case ERROR: case ERROR:
case WARN: case WARN:
case INFO: case INFO:
assertMsg(logger, Level.ERROR, "ACCESS_DENIED\thost=[local[_host]], action=[_action], principal=[_username]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.ERROR, "ACCESS_DENIED\thost=[local[_host]], principal=[_username], action=[_action], indices=[idx1,idx2]");
} else {
assertMsg(logger, Level.ERROR, "ACCESS_DENIED\thost=[local[_host]], principal=[_username], action=[_action]");
}
break; break;
case DEBUG: case DEBUG:
case TRACE: case TRACE:
assertMsg(logger, Level.DEBUG, "ACCESS_DENIED\thost=[local[_host]], action=[_action], principal=[_username], request=[mock-message]"); if (message instanceof IndicesRequest) {
assertMsg(logger, Level.DEBUG, "ACCESS_DENIED\thost=[local[_host]], principal=[_username], action=[_action], indices=[idx1,idx2], request=[mock-message]");
} else {
assertMsg(logger, Level.DEBUG, "ACCESS_DENIED\thost=[local[_host]], principal=[_username], action=[_action], request=[mock-message]");
}
} }
} }
} }
@ -194,6 +237,27 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
} }
} }
private static class MockIndicesRequest extends TransportMessage<MockIndicesRequest> implements IndicesRequest {
private MockIndicesRequest() {
remoteAddress(new LocalTransportAddress("_host"));
}
@Override
public String[] indices() {
return new String[] { "idx1", "idx2" };
}
@Override
public IndicesOptions indicesOptions() {
return IndicesOptions.strictExpandOpenAndForbidClosed();
}
@Override
public String toString() {
return "mock-message";
}
}
private static class MockToken implements AuthenticationToken { private static class MockToken implements AuthenticationToken {
@Override @Override

View File

@ -114,7 +114,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public static Settings buildAdSettings(String ldapUrl, String adDomainName) { public static Settings buildAdSettings(String ldapUrl, String adDomainName) {
return ImmutableSettings.builder() return ImmutableSettings.builder()
.putArray(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl) .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName) .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.build(); .build();
} }

View File

@ -0,0 +1,78 @@
/*
* 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.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.hamcrest.Matchers;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import static org.hamcrest.Matchers.equalTo;
public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
//LdapModule will set this up as a singleton normally
LdapSslSocketFactory.init(ImmutableSettings.builder()
.put("shield.authc.ldap.truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()))
.build());
}
@Test
public void testConfigure_1https(){
String[] urls = new String[]{"ldaps://example.com:636"};
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2https(){
String[] urls = new String[]{"ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636"};
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2http(){
String[] urls = new String[]{"ldap://primary.example.com:392", "LDAP://secondary.example.com:10392"};
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1httpS_1http(){
String[] urls = new String[]{"LDAPS://primary.example.com:636", "ldap://secondary.example.com:392"};
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1http_1https(){
String[] urls = new String[]{"ldap://primary.example.com:392", "ldaps://secondary.example.com:636"};
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
}