From 2ed4dd7fb677809a3a5f104997ea4b6c76aea53e Mon Sep 17 00:00:00 2001 From: c-a-m Date: Thu, 28 Aug 2014 08:13:14 -0600 Subject: [PATCH] ldap: Adds OpenLdap and Active Directory tests, and refactors SSLConfig SSLConfig is split into SSLConfig and SSLTrustConfig. OpenLdapTests and ActiveDirectory tests connect via TLS to EC2 instances. Original commit: elastic/x-pack-elasticsearch@ea38e58dea645523cfa786e09f7aa3b965656926 --- .../ActiveDirectoryConnectionFactory.java | 12 +- .../shield/authc/ldap/LdapModule.java | 1 + .../authc/ldap/LdapSslSocketFactory.java | 108 ++++++++++++++++++ .../ldap/StandardLdapConnectionFactory.java | 11 +- .../shield/transport/ssl/SSLConfig.java | 36 +----- .../shield/transport/ssl/SSLTrustConfig.java | 82 +++++++++++++ .../ldap/ActiveDirectoryFactoryTests.java | 108 ++++++++++++++---- .../authc/ldap/LdapConnectionTests.java | 2 + .../shield/authc/ldap/LdapRealmTest.java | 77 +------------ .../shield/authc/ldap/LdapTest.java | 24 ++++ .../shield/authc/ldap/OpenLdapTests.java | 51 +++++++++ .../shield/test/ShieldIntegrationTest.java | 17 +++ .../shield/authc/ldap/ldaptrust.jks | Bin 0 -> 2810 bytes tests.policy | 2 + 14 files changed, 399 insertions(+), 132 deletions(-) create mode 100644 src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java create mode 100644 src/main/java/org/elasticsearch/shield/transport/ssl/SSLTrustConfig.java create mode 100644 src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java create mode 100644 src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryConnectionFactory.java b/src/main/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryConnectionFactory.java index fd46d89148e..2b30eaa2151 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryConnectionFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryConnectionFactory.java @@ -50,12 +50,14 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen int port = componentSettings.getAsInt(AD_PORT, 389); String[] ldapUrls = componentSettings.getAsArray(URLS_SETTING, new String[] { "ldap://" + domainName + ":" + port }); - - sharedLdapEnv = ImmutableMap.builder() + ImmutableMap.Builder builder = ImmutableMap.builder() .put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") .put(Context.PROVIDER_URL, Strings.arrayToCommaDelimitedString(ldapUrls)) - .put(Context.REFERRAL, "follow") - .build(); + .put(Context.REFERRAL, "follow"); + + LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder); + + sharedLdapEnv = builder.build(); } /** @@ -91,7 +93,7 @@ public class ActiveDirectoryConnectionFactory extends AbstractComponent implemen throw new LdapException("Search for user [" + userName + "] by principle name yielded multiple results"); } - throw new LdapException("Search for user [" + userName + "] yielded no results"); + throw new LdapException("Search for user [" + userName + "], search root [" + userSearchDN + "] yielded no results"); } catch (NamingException e) { throw new LdapException("Unable to authenticate user [" + userName + "] to active directory domain ["+ domainName +"]", e); diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapModule.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapModule.java index c6f4b718e53..d8998495b53 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapModule.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapModule.java @@ -28,6 +28,7 @@ public class LdapModule extends AbstractShieldModule.Node { protected void configureNode() { if (enabled) { bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton(); + bind(LdapSslSocketFactory.class).asEagerSingleton(); bind(LdapGroupToRoleMapper.class).asEagerSingleton(); String mode = settings.getComponentSettings(LdapModule.class).get("mode", "ldap"); if ("ldap".equals(mode)) { diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java new file mode 100644 index 00000000000..c165259bf70 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java @@ -0,0 +1,108 @@ +/* + * 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.ElasticsearchException; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.shield.transport.ssl.SSLTrustConfig; + +import javax.net.SocketFactory; +import java.io.IOException; +import java.io.Serializable; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Locale; + +/** + * This factory is needed for JNDI configuration for LDAP connections. It wraps a single instance of a static + * factory that is initiated by the settings constructor + */ +public class LdapSslSocketFactory extends SocketFactory { + private static SocketFactory socketFactory; + private static ESLogger logger = ESLoggerFactory.getLogger(LdapSslSocketFactory.class.getName()); + + /** + * This should only be invoked once to establish a static instance. + */ + @Inject + public LdapSslSocketFactory(Settings settings) { + if (socketFactory == null) { + logger.error("LdapSslSocketFactory already configured, this change could lead to threading issues"); + } + Settings componentSettings = settings.getComponentSettings(getClass()); + SSLTrustConfig sslConfig = new SSLTrustConfig(componentSettings, settings.getByPrefix("shield.ssl.")); + socketFactory = sslConfig.createSSLSocketFactory(); + } + + /** + * This is invoked by JNDI and the returned SocketFactory must be an LdapSslSocketFactory object + * @return + */ + public static SocketFactory getDefault() { + return new LdapSslSocketFactory(); + } + + public static boolean initialized() { + return socketFactory != null; + } + + LdapSslSocketFactory(){ + if (socketFactory == null){ + throw new ElasticsearchException("Attempt to construct an uninitialized LdapSslSocketFactory"); + } + } + + //The following methods are all wrappers around the static instance of socketFactory + + @Override + public Socket createSocket(String s, int i) throws IOException { + return socketFactory.createSocket(s, i); + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i2) throws IOException { + return socketFactory.createSocket(s, i, inetAddress, i2); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) throws IOException { + return socketFactory.createSocket(inetAddress, i); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress2, int i2) throws IOException { + return socketFactory.createSocket(inetAddress, i, inetAddress2, i2); + } + + /** + * If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the + * @param ldapUrls + * @param builder set of jndi properties, that will + */ + public static ImmutableMap.Builder configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder builder) { + boolean needsSSL = false; + for(String url: ldapUrls){ + if (url.toLowerCase(Locale.getDefault()).startsWith("ldaps://")) { + needsSSL = true; + break; + } + } + if (needsSSL) { + if (socketFactory != null) { + builder.put("java.naming.ldap.factory.socket", LdapSslSocketFactory.class.getName()); + } else { + logger.warn("LdapSslSocketFactory not initialized and won't be used for LDAP connections"); + } + } else { + logger.debug("LdapSslSocketFactory not used for LDAP connections"); + } + return builder; + } +} diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/StandardLdapConnectionFactory.java b/src/main/java/org/elasticsearch/shield/authc/ldap/StandardLdapConnectionFactory.java index adda8b27fd7..7884c2fd710 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/StandardLdapConnectionFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/StandardLdapConnectionFactory.java @@ -51,12 +51,15 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements if (ldapUrls == null) { throw new ShieldException("Missing required ldap setting [" + URLS_SETTING + "]"); } - sharedLdapEnv = ImmutableMap.builder() + + ImmutableMap.Builder builder = ImmutableMap.builder() .put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") .put(Context.PROVIDER_URL, Strings.arrayToCommaDelimitedString(ldapUrls)) - .put(Context.REFERRAL, "follow") - .build(); + .put(Context.REFERRAL, "follow"); + LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder); + + sharedLdapEnv = builder.build(); groupSearchDN = componentSettings.get(GROUP_SEARCH_BASEDN_SETTING); findGroupsByAttribute = groupSearchDN == null; groupSubTreeSearch = componentSettings.getAsBoolean(GROUP_SEARCH_SUBTREE_SETTING, false); @@ -85,7 +88,7 @@ public class StandardLdapConnectionFactory extends AbstractComponent implements return new LdapConnection(ctx, dn, findGroupsByAttribute, groupSubTreeSearch, groupSearchDN); } catch (NamingException e) { - logger.warn("Failed ldap authentication with user template [{}], dn [{}]", template, dn); + logger.warn("Failed ldap authentication with user template [{}], dn [{}]", e, template, dn ); } } diff --git a/src/main/java/org/elasticsearch/shield/transport/ssl/SSLConfig.java b/src/main/java/org/elasticsearch/shield/transport/ssl/SSLConfig.java index d013f23ca7b..fd98e05c0d8 100644 --- a/src/main/java/org/elasticsearch/shield/transport/ssl/SSLConfig.java +++ b/src/main/java/org/elasticsearch/shield/transport/ssl/SSLConfig.java @@ -29,41 +29,28 @@ public class SSLConfig { private String[] ciphers; public SSLConfig(Settings componentSettings, Settings defaultSettings) { + SSLTrustConfig sslTrustConfig = new SSLTrustConfig(componentSettings, defaultSettings); + this.clientAuth = componentSettings.getAsBoolean("require.client.auth", defaultSettings.getAsBoolean("require.client.auth", true)); String keyStore = componentSettings.get("keystore", defaultSettings.get("keystore", System.getProperty("javax.net.ssl.keyStore"))); String keyStorePassword = componentSettings.get("keystore_password", defaultSettings.get("keystore_password", System.getProperty("javax.net.ssl.keyStorePassword"))); String keyStoreAlgorithm = componentSettings.get("keystore_algorithm", defaultSettings.get("keystore_algorithm", System.getProperty("ssl.KeyManagerFactory.algorithm"))); - String trustStore = componentSettings.get("truststore", defaultSettings.get("truststore", System.getProperty("javax.net.ssl.trustStore"))); - String trustStorePassword = componentSettings.get("truststore_password", defaultSettings.get("truststore_password", System.getProperty("javax.net.ssl.trustStorePassword"))); - String trustStoreAlgorithm = componentSettings.get("truststore_algorithm", defaultSettings.get("truststore_algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm"))); this.ciphers = componentSettings.getAsArray("ciphers", defaultSettings.getAsArray("ciphers", DEFAULT_CIPHERS)); if (keyStore == null) { throw new ElasticsearchException("SSL Enabled, but keystore unconfigured"); } - if (trustStore == null) { - throw new ElasticsearchException("SSL Enabled, but truststore unconfigured"); - } - if (keyStoreAlgorithm == null) { keyStoreAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); } - if (trustStoreAlgorithm == null) { - trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - } - - logger.debug("using keyStore[{}], keyAlgorithm[{}], trustStore[{}], trustAlgorithm[{}]", keyStore, keyStoreAlgorithm, trustStore, trustStoreAlgorithm); + logger.debug("using keyStore[{}], keyAlgorithm[{}], ", keyStore, keyStoreAlgorithm); if (!new File(keyStore).exists()) { throw new ElasticsearchSSLException("Keystore at path ["+ keyStore +"] does not exist"); } - if (!new File(trustStore).exists()) { - throw new ElasticsearchSSLException("Truststore at path ["+ keyStore +"] does not exist"); - } - KeyStore ks = null; KeyManagerFactory kmf = null; try (FileInputStream in = new FileInputStream(keyStore)){ @@ -78,26 +65,11 @@ public class SSLConfig { throw new ElasticsearchSSLException("Failed to initialize a KeyManagerFactory", e); } - TrustManager[] trustManagers = null; - try (FileInputStream in = new FileInputStream(trustStore)) { - // Load TrustStore - ks.load(in, trustStorePassword.toCharArray()); - - // Initialize a trust manager factory with the trusted store - TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm); - trustFactory.init(ks); - - // Retrieve the trust managers from the factory - trustManagers = trustFactory.getTrustManagers(); - } catch (Exception e) { - throw new ElasticsearchException("Failed to initialize a TrustManagerFactory", e); - } - // Initialize sslContext try { String algorithm = componentSettings.get("context_algorithm", defaultSettings.get("shield.ssl.context_algorithm", "TLS")); sslContext = SSLContext.getInstance(algorithm); - sslContext.init(kmf.getKeyManagers(), trustManagers, null); + sslContext.init(kmf.getKeyManagers(), sslTrustConfig.getTrustManagers(), null); } catch (Exception e) { throw new ElasticsearchSSLException("Failed to initialize the SSLContext", e); } diff --git a/src/main/java/org/elasticsearch/shield/transport/ssl/SSLTrustConfig.java b/src/main/java/org/elasticsearch/shield/transport/ssl/SSLTrustConfig.java new file mode 100644 index 00000000000..a8738433020 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/transport/ssl/SSLTrustConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.transport.ssl; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Settings; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; + +/** + * + */ +public class SSLTrustConfig { + + private static final ESLogger logger = Loggers.getLogger(SSLTrustConfig.class); + private final TrustManager[] trustManagers; + private final String sslContextAlgorithm; + + private SSLContext sslContext; + + public SSLTrustConfig(Settings componentSettings, Settings defaultSettings) { + this.sslContextAlgorithm = componentSettings.get("context_algorithm", defaultSettings.get("shield.ssl.context_algorithm", "TLS")); + String trustStore = componentSettings.get("truststore", defaultSettings.get("truststore", System.getProperty("javax.net.ssl.trustStore"))); + String trustStorePassword = componentSettings.get("truststore_password", defaultSettings.get("truststore_password", System.getProperty("javax.net.ssl.trustStorePassword"))); + String trustStoreAlgorithm = componentSettings.get("truststore_algorithm", defaultSettings.get("truststore_algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm"))); + + if (trustStore == null) { + throw new ElasticsearchException("SSL Enabled, but truststore unconfigured"); + } + + if (trustStoreAlgorithm == null) { + trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + } + + logger.debug("using trustStore[{}], trustAlgorithm[{}]", trustStore, trustStoreAlgorithm); + + if (!new File(trustStore).exists()) { + throw new ElasticsearchSSLException("Truststore at path ["+ trustStore +"] does not exist"); + } + + try (FileInputStream in = new FileInputStream(trustStore)) { + // Load TrustStore + KeyStore ks = KeyStore.getInstance("jks"); + ks.load(in, trustStorePassword == null ? null : trustStorePassword.toCharArray()); + + // Initialize a trust manager factory with the trusted store + TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm); + trustFactory.init(ks); + + // Retrieve the trust managers from the factory + trustManagers = trustFactory.getTrustManagers(); + } catch (Exception e) { + throw new ElasticsearchException("Failed to initialize a TrustManagerFactory", e); + } + } + + public SSLSocketFactory createSSLSocketFactory() { + // Initialize sslContext + try { + sslContext = SSLContext.getInstance(sslContextAlgorithm); + sslContext.init(null, trustManagers, null); + } catch (Exception e) { + throw new ElasticsearchSSLException("Failed to initialize the SSLContext", e); + } + return sslContext.getSocketFactory(); + } + + public TrustManager[] getTrustManagers() { + return trustManagers; + } +} diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java index dd50fd70831..9f611c0ba70 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java @@ -7,62 +7,130 @@ 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.Ignore; +import org.elasticsearch.test.junit.annotations.Network; +import org.hamcrest.Matchers; +import org.junit.BeforeClass; import org.junit.Test; +import java.io.File; +import java.net.URISyntaxException; import java.util.List; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.core.IsCollectionContaining.hasItem; public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase { - public static final String OPEN_LDAP_URL = "ldap://ad.es.com:389"; - public static final String AD_LDAP_URL = "ldap://54.213.145.20:389"; - public static final String PASSWORD = "4joD8LmWcrEfRa&p"; + public static final String AD_LDAP_URL = "ldaps://54.213.145.20:636"; + public static final String PASSWORD = "NickFuryHeartsES"; + public static final String AD_DOMAIN = "ad.test.elasticsearch.com"; + public static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.'; - @Ignore - @Test + @BeforeClass + public static void setTrustStore() throws URISyntaxException { + new LdapSslSocketFactory(ImmutableSettings.builder() + .put(SETTINGS_PREFIX + "truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI())) + .build()); + } + + + @Test @Network public void testAdAuth() { ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory( - buildAdSettings(AD_LDAP_URL, "ad.test.elasticsearch.com")); + buildAdSettings(AD_LDAP_URL, AD_DOMAIN)); String userName = "ironman"; - LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD)); String userDN = ldap.getAuthenticatedUserDn(); - //System.out.println("userPassword check:"+ldap.checkPassword(userDn, userPass)); + + List groups = ldap.getGroupsFromUserAttrs(userDN); + assertThat(groups, containsInAnyOrder( + containsString("Geniuses"), + containsString("Billionaire"), + containsString("Playboy"), + containsString("Philanthropists"), + containsString("Avengers"), + containsString("SHIELD"))); + + } + + @Test @Network + public void testAdAuth_avengers() { + ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory( + buildAdSettings(AD_LDAP_URL, AD_DOMAIN)); + + String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; + for(String user: users) { + LdapConnection ldap = connectionFactory.bind(user, SecuredStringTests.build(PASSWORD)); + assertThat("group avenger test for user "+user, ldap.getGroups(), hasItem(Matchers.containsString("Avengers"))); + ldap.close(); + } + } + + @Test @Network + public void testAdAuth_specificUserSearch() { + ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory( + buildAdSettings(AD_LDAP_URL, AD_DOMAIN, + "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com")); + + String userName = "hulk"; + LdapConnection ldap = connectionFactory.bind(userName, SecuredStringTests.build(PASSWORD)); + String userDN = ldap.getAuthenticatedUserDn(); List groups = ldap.getGroupsFromUserAttrs(userDN); System.out.println("groups: "+groups); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); + } - @Ignore - @Test + @Test @Network public void testAD_standardLdapConnection(){ - String groupSearchBase = "dc=ad,dc=test,dc=elasticsearch,dc=com"; - String userTemplate = "cn={0},cn=Users,dc=ad,dc=test,dc=elasticsearch,dc=com"; + String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; + String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; boolean isSubTreeSearch = true; StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory( LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch)); - String user = "Tony Stark"; + String user = "Bruce Banner"; LdapConnection ldap = connectionFactory.bind(user, SecuredStringTests.build(PASSWORD)); List groups = ldap.getGroupsFromUserAttrs(ldap.getAuthenticatedUserDn()); List groups2 = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn()); - assertThat(groups, containsInAnyOrder(containsString("upchuckers"), containsString("localDistribution"))); - assertThat(groups2, containsInAnyOrder(containsString("upchuckers"), containsString("localDistribution"))); + System.out.println(groups); + System.out.println(groups2); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); + assertThat(groups2, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); } - 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) + .build(); + } + + public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN) { return ImmutableSettings.builder() - .putArray(LdapTest.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl) - .put(LdapTest.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName) + .putArray(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl) + .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName) + .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN) .build(); } } diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java index 55fc6316c36..148daed83ac 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java @@ -12,6 +12,8 @@ import org.elasticsearch.test.ElasticsearchTestCase; import org.junit.Rule; import org.junit.Test; +import java.io.File; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java index 7da5caa0487..0b59fe9fea8 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java @@ -7,28 +7,22 @@ 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.threadpool.ThreadPool; -import org.elasticsearch.watcher.ResourceWatcherService; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; 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"; @@ -51,7 +45,7 @@ public class LdapRealmTest extends LdapTest { String groupSearchBase = "o=sevenSeas"; boolean isSubTreeSearch = true; String userTemplate = VALID_USER_TEMPLATE; - Settings settings = LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch); + Settings settings = buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch); StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(settings); LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController); @@ -66,7 +60,7 @@ public class LdapRealmTest extends LdapTest { boolean isSubTreeSearch = false; String userTemplate = VALID_USER_TEMPLATE; StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory( - LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch)); + buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch)); LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController); @@ -81,7 +75,7 @@ public class LdapRealmTest extends LdapTest { boolean isSubTreeSearch = true; String userTemplate = VALID_USER_TEMPLATE; StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory( - LdapConnectionTests.buildLdapSettings( apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) ); + buildLdapSettings( apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) ); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm( buildCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController); @@ -98,7 +92,7 @@ public class LdapRealmTest extends LdapTest { boolean isSubTreeSearch = true; String userTemplate = VALID_USER_TEMPLATE; StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory( - LdapConnectionTests.buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) ); + buildLdapSettings(apacheDsRule.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) ); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper(), restController); @@ -108,63 +102,4 @@ public class LdapRealmTest extends LdapTest { //verify two and only two binds -> caching is disabled verify(ldapFactory, times(2)).bind(anyString(), any(SecuredString.class)); } - - @Ignore - @Test - public void testAD() { - String adDomain = "ad.test.elasticsearch.com"; - String userSearchBaseDN = "dc=ad,dc=es,dc=com"; - - ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory( - ActiveDirectoryFactoryTests.buildAdSettings(AD_URL, adDomain)); - - 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")); - } - - @Ignore - @Test - public void testAD_defaults() { - //only set the adDomain, and see if it infers the rest correctly - String adDomain = AD_IP; - Settings settings = ImmutableSettings.builder() - .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomain) - .build(); - - ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory( settings ); - 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")); - } - - - - private Settings buildNonCachingSettings() { - return ImmutableSettings.builder() - .put("shield.authc.ldap."+LdapRealm.CACHE_TTL, -1) - .build(); - } - - private Settings buildCachingSettings() { - return ImmutableSettings.builder() - .put("shield.authc.ldap."+LdapRealm.CACHE_TTL, 100000000) - .put("shield.authc.ldap." + LdapRealm.CACHE_MAX_USERS, 10) - .build(); - } - - private LdapGroupToRoleMapper buildGroupAsRoleMapper() { - Settings settings = ImmutableSettings.builder() - .put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true) - .build(); - - return new LdapGroupToRoleMapper(settings, - new Environment(settings), - new ResourceWatcherService(settings, new ThreadPool("test"))); - } } diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapTest.java index 726a98a3c33..3e27969d572 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapTest.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapTest.java @@ -7,7 +7,10 @@ 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.test.ElasticsearchTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.rules.RuleChain; @@ -36,4 +39,25 @@ public abstract class LdapTest extends ElasticsearchTestCase { .put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase) .put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build(); } + + protected Settings buildNonCachingSettings() { + return ImmutableSettings.builder() + .put("shield.authc.ldap."+LdapRealm.CACHE_TTL, -1) + .build(); + } + + protected Settings buildCachingSettings() { + return ImmutableSettings.builder() + .build(); + } + + protected LdapGroupToRoleMapper buildGroupAsRoleMapper() { + Settings settings = ImmutableSettings.builder() + .put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true) + .build(); + + return new LdapGroupToRoleMapper(settings, + new Environment(settings), + new ResourceWatcherService(settings, new ThreadPool("test"))); + } } diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java new file mode 100644 index 00000000000..8bdc22fb7e3 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java @@ -0,0 +1,51 @@ +/* + * 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.test.ElasticsearchTestCase; +import org.elasticsearch.test.junit.annotations.Network; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.net.URISyntaxException; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; + +public class OpenLdapTests extends ElasticsearchTestCase { + public static final String OPEN_LDAP_URL = "ldaps://54.200.235.244:636"; + public static final String PASSWORD = "NickFuryHeartsES"; + public static final String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.'; + + @BeforeClass + public static void setTrustStore() throws URISyntaxException { + //LdapModule will set this up as a singleton normally + new LdapSslSocketFactory(ImmutableSettings.builder() + .put(SETTINGS_PREFIX + "truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI())) + .build()); + } + + @Test @Network + public void test_standardLdapConnection_uid(){ + //openldap does not use cn as naming attributes by default + + String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + boolean isSubTreeSearch = true; + StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory( + LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, isSubTreeSearch)); + + String[] users = new String[]{"blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor"}; + for(String user: users) { + LdapConnection ldap = connectionFactory.bind(user, PASSWORD.toCharArray()); + assertThat(ldap.getGroups(), hasItem(containsString("Avengers"))); + ldap.close(); + } + } + +} diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index 578a2fedc23..a15cfb2759c 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -74,6 +74,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest .put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL)) .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode")) .put("shield.audit.enabled", true) + .put(getSSLSettingsForLdap("/org/elasticsearch/shield/authc/ldap/ldaptrust.jks", "changeit")) .put("plugins.load_classpath_plugins", false); if (OsUtils.MAC) { @@ -148,6 +149,22 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest return builder.build(); } + protected Settings getSSLSettingsForLdap(String resourcePathToStore, String password) { + File store; + try { + store = new File(getClass().getResource(resourcePathToStore).toURI()); + assertThat(store.exists(), is(true)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + ImmutableSettings.Builder builder = settingsBuilder() + .put("shield.authc.ldap.truststore_password", password) + .put("shield.authc.ldap.truststore", store.getPath()); + + return builder.build(); + } + protected File newFolder() { try { return tmpFolder.newFolder(); diff --git a/src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks new file mode 100644 index 0000000000000000000000000000000000000000..fcd8615ba5b4545a7cdf7bbee9504e53bdb0c520 GIT binary patch literal 2810 zcmcJR3se(V8pmfQlK@gA5Q-uO5JZ>v4F*9Xg(N^h5RpJF5f(xcBnkvVK#deh7D04< zr3w{MkcZ#{>7qS8uw4s25EP;Ks^BB!;Hv^PAU?`tXGq1k$8&buvwM=vz5nmtnVWC! ze}3PL_mB5O5QI$qh~i`cFAaj=AfMgU0*Lsfs|Q#FL8u{6pn5=o43-lR7{R{4h-BcH zAC&*f2!@G72n5m$fdPpSg_0;p6o+OG%y2P{VkAuFWk^N*3?Wa#PofzCLp*LsL8ZbB zDUA%KCJ_oP;PEYa+{CLx^k*U@$^OPK;^`- z6}SCVuSD}tpL@tXxuV6p^UGGSIYhI1C>3ndkUPl(Wd|Mtuj~3pzSw_0=0Yv!@wv0U z5#zywzs4V?WLc-C`};e2sZAPQ?&V&IQkC1?cY2i2f0#%OK{KhYsWdqbrX0d$}p~UUUYrX#cTpwS%i-pk&Wj`)BoeFO&R@#Eh%YXZ_ak z(=$Ss7gb%j!!a-A?AWmlLB1Ug9qn6d_8+Wlq!8lYh(~T2INCg6G!-R{(4Cc^iOhSTtdZ4j$=NQ5+C;9lWrHky*bWtglw7kxHOqPahWdko5Qa!o z(^?QKqm3S1QWf;SGNHl%dOkg7oq3KXPuClm%`tDlP}MKQmVgTK@tMuH%F>i$b$<`8 zQQo!9Xrix5E1?e3lNx&B_D=LiC#fzw+!yBNsW(XHD0keLJ*YpTp*HPw*FSK>!Su9$ z-jaxbuwh^6-v$=F`mXh2&Y+TGxa_8)ZQHVl+pP4?-g#$+>^k&;%|A;nEsi%B{;X`q z)@a7s#|tx9wI_DXZSYxM#UHU~TN(4PIJo#m_xUw6F4ASy-fbMU1K#3R_oy#Wb&W#W z*Q`%)QpqeERKzckR{i5G;;S+la#IAtOow4KPb%&`H z+#NRP-X*2KF5R=pHk0%{e9}r?GcoZkb-&XV>u1q%rzT;6@B~^)bo)$Ef>Z<)BqdM~ zWm-y5Lhz}d-t7!Wxr!02k15?7YKU zF$1O_3~Om(3dkQsvHwW30<*PG{-->FOF$@-6B*3r1TZ7mK}>%aHdBW;3+Wzo&-c91 zO5jCX26#gSw_SVkRw48YR-KLAGG|sCF z8$1NI(nJ67s0gOl78iN(3ufrgp^zH$NE4fVab6jc)bwD?}5t#2Xgs^g;{ zTUH0D9ja}EY^ry=2kz-R@Pze)@#d8`>z#vHJ@r|?jX75-=F*N8{!9{OSI@PFj?@+9 zEGARJp`+XiN~mTqrLJVGDxW)RfvIxEcwcL1Z{7pa$wtPVCQ1M8TfztHd)ip)UY}C%ILEEP4xZ@ZRJO%2*H&aDR-dN5d^zSzmyO!>25u+(G+S>>6FC;Mx#Qanf3*)kSGOv` z&wZJ>P1lw}^1cYYTtAsl#J0$;*F6af;w$~W*>xf2XJy5lhI{1!!>sf7vQ1_d<*d6b zmoDy8-F0vyzMiLl=ETypXW@Fh0Q_4%0ddb-}*`;3(