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 00000000000..fcd8615ba5b Binary files /dev/null and b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks differ diff --git a/tests.policy b/tests.policy index ad06580d8b8..52d0a97cab4 100644 --- a/tests.policy +++ b/tests.policy @@ -39,4 +39,6 @@ grant { //this shouldn't be in a production environment, just to run tests: permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler"; + + permission javax.net.ssl.SSLPermission "setDefaultSSLContext"; };