diff --git a/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java b/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java index 0653d16acb2..10c1f7f275f 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/ldap/ConnectionFactory.java @@ -42,7 +42,7 @@ public abstract class ConnectionFactory ldapEnv = new Hashtable<>(); + ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple"); + ldapEnv.put(Context.SECURITY_PRINCIPAL, BRUCE_BANNER_DN); + ldapEnv.put(Context.SECURITY_CREDENTIALS, ActiveDirectoryFactoryTests.PASSWORD); + ldapEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + ldapEnv.put(Context.PROVIDER_URL, ActiveDirectoryFactoryTests.AD_LDAP_URL); + ldapEnv.put(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName()); + ldapEnv.put("java.naming.ldap.attributes.binary", "tokenGroups"); + ldapEnv.put(Context.REFERRAL, "follow"); + ldapContext = new InitialDirContext(ldapEnv); + + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + ldapContext.close(); + LdapSslSocketFactory.clear(); + } + + @Test + public void testResolveSubTree() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("scope", SearchScope.SUB_TREE) + .build(); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"), + containsString("Users"), + containsString("Domain Users"), + containsString("Supers"))); + } + + @Test + public void testResolveOneLevel() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("scope", SearchScope.ONE_LEVEL) + .put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com") + .build(); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, hasItem(containsString("Users"))); + } + + @Test + public void testResolveBaseLevel() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("scope", SearchScope.BASE) + .put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com") + .build(); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, hasItem(containsString("Users"))); + } + + @Test + public void testBuildGroupQuery() throws Exception { + //test a user with no assigned groups, other than the default groups + { + String[] expectedSids = new String[]{ + "S-1-5-32-545", //Default Users group + "S-1-5-21-3510024162-210737641-214529065-513" //Default Domain Users group + }; + String query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapContext, "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10)); + assertValidSidQuery(query, expectedSids); + } + + //test a user of one groups + { + String[] expectedSids = new String[]{ + "S-1-5-32-545", //Default Users group + "S-1-5-21-3510024162-210737641-214529065-513", //Default Domain Users group + "S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group + String query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapContext, "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10)); + assertValidSidQuery(query, expectedSids); + } + + //test a user of many groups + { + String[] expectedSids = new String[]{ + "S-1-5-32-545", //Default Users Group + "S-1-5-21-3510024162-210737641-214529065-513", //Default Domain Users group + "S-1-5-21-3510024162-210737641-214529065-1123", //Supers + "S-1-5-21-3510024162-210737641-214529065-1110", //Philanthropists + "S-1-5-21-3510024162-210737641-214529065-1108", //Geniuses + "S-1-5-21-3510024162-210737641-214529065-1106", //SHIELD + "S-1-5-21-3510024162-210737641-214529065-1105"};//Avengers + String query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapContext, "CN=Bruce Banner, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10)); + assertValidSidQuery(query, expectedSids); + } + } + + private void assertValidSidQuery(String query, String[] expectedSids) { + Pattern sidQueryPattern = Pattern.compile("\\(\\|(\\(objectSid=S(-\\d+)+\\))+\\)"); + assertThat("[" + query + "] didn't match the search filter pattern", sidQueryPattern.matcher(query).matches(), is(true)); + for(String sid: expectedSids) { + assertThat(query, containsString(sid)); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/SearchGroupsResolverTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/SearchGroupsResolverTest.java new file mode 100644 index 00000000000..36c350662b1 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/SearchGroupsResolverTest.java @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.authc.ldap; + +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory; +import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory; +import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; +import org.elasticsearch.shield.authc.support.ldap.SearchScope; +import org.elasticsearch.shield.ssl.SSLService; +import org.elasticsearch.shield.support.NoOpLogger; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.test.junit.annotations.Network; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.naming.Context; +import javax.naming.directory.InitialDirContext; +import java.io.Serializable; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Hashtable; +import java.util.List; + +import static org.hamcrest.Matchers.*; + +@Network +public class SearchGroupsResolverTest extends ElasticsearchTestCase { + + public static final String BRUCE_BANNER_DN = "uid=hulk,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; + private InitialDirContext ldapContext; + + @Before + public void setup() throws Exception { + super.setUp(); + Path keystore = Paths.get(SearchGroupsResolverTest.class.getResource("../support/ldap/ldaptrust.jks").toURI()).toAbsolutePath(); + + /* + * Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext. + * If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname + * verification tests since a re-established connection does not perform hostname verification. + */ + AbstractLdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder() + .put("shield.ssl.keystore.path", keystore) + .put("shield.ssl.keystore.password", "changeit") + .build())); + + Hashtable ldapEnv = new Hashtable<>(); + ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple"); + ldapEnv.put(Context.SECURITY_PRINCIPAL, BRUCE_BANNER_DN); + ldapEnv.put(Context.SECURITY_CREDENTIALS, OpenLdapTests.PASSWORD); + ldapEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + ldapEnv.put(Context.PROVIDER_URL, OpenLdapTests.OPEN_LDAP_URL); + ldapEnv.put(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName()); + ldapEnv.put("java.naming.ldap.attributes.binary", "tokenGroups"); + ldapEnv.put(Context.REFERRAL, "follow"); + ldapContext = new InitialDirContext(ldapEnv); + + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + ldapContext.close(); + LdapSslSocketFactory.clear(); + } + + @Test + public void testResolveSubTree() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); + } + + @Test + public void testResolveOneLevel() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("base_dn", "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("scope", SearchScope.ONE_LEVEL) + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); + } + + @Test + public void testResolveBase() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("base_dn", "cn=Avengers,ou=People,dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("scope", SearchScope.BASE) + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, hasItem(containsString("Avengers"))); + } + + @Test + public void testResolveCustomFilter() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("filter", "(&(objectclass=posixGroup)(memberUID={0}))") + .put("user_attribute", "uid") + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + assertThat(groups, hasItem(containsString("Geniuses"))); + } + + @Test + public void testReadUserAttribute() throws Exception { + { + Settings settings = ImmutableSettings.builder().put("user_attribute", "uid").build(); + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + assertThat(resolver.readUserAttribute(ldapContext, BRUCE_BANNER_DN), is("hulk")); + } + + { + Settings settings = ImmutableSettings.builder().put("user_attribute", "cn").build(); + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + assertThat(resolver.readUserAttribute(ldapContext, BRUCE_BANNER_DN), is("Bruce Banner")); + } + + try { + Settings settings = ImmutableSettings.builder().put("user_attribute", "doesntExists").build(); + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + resolver.readUserAttribute(ldapContext, BRUCE_BANNER_DN); + fail("searching for a non-existing attribute should throw an LdapException"); + } catch (LdapException e) { + assertThat(e.getMessage(), containsString("No results returned")); + } + + try { + Settings settings = ImmutableSettings.builder().put("user_attribute", "userPassword").build(); + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + resolver.readUserAttribute(ldapContext, BRUCE_BANNER_DN); + fail("searching for a binary attribute should throw an LdapException"); + } catch (LdapException e) { + assertThat(e.getMessage(), containsString("is not of type String")); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/UserAttributeGroupsResolverTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/UserAttributeGroupsResolverTest.java new file mode 100644 index 00000000000..839127f2233 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/UserAttributeGroupsResolverTest.java @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.authc.ldap; + +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryFactoryTests; +import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory; +import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory; +import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory; +import org.elasticsearch.shield.ssl.SSLService; +import org.elasticsearch.shield.support.NoOpLogger; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.test.junit.annotations.Network; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.naming.Context; +import javax.naming.directory.InitialDirContext; +import java.io.Serializable; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Hashtable; +import java.util.List; + +import static org.hamcrest.Matchers.*; + +@Network +public class UserAttributeGroupsResolverTest extends ElasticsearchTestCase { + public static final String BRUCE_BANNER_DN = "cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; + private InitialDirContext ldapContext; + + @Before + public void setUp() throws Exception { + super.setUp(); + Path keystore = Paths.get(UserAttributeGroupsResolverTest.class.getResource("../support/ldap/ldaptrust.jks").toURI()).toAbsolutePath(); + + /* + * Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext. + * If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname + * verification tests since a re-established connection does not perform hostname verification. + */ + AbstractLdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder() + .put("shield.ssl.keystore.path", keystore) + .put("shield.ssl.keystore.password", "changeit") + .build())); + + Hashtable ldapEnv = new Hashtable<>(); + ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple"); + ldapEnv.put(Context.SECURITY_PRINCIPAL, BRUCE_BANNER_DN); + ldapEnv.put(Context.SECURITY_CREDENTIALS, ActiveDirectoryFactoryTests.PASSWORD); + ldapEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + ldapEnv.put(Context.PROVIDER_URL, ActiveDirectoryFactoryTests.AD_LDAP_URL); + ldapEnv.put(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName()); + ldapEnv.put("java.naming.ldap.attributes.binary", "tokenGroups"); + ldapEnv.put(Context.REFERRAL, "follow"); + ldapContext = new InitialDirContext(ldapEnv); + + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + ldapContext.close(); + LdapSslSocketFactory.clear(); + } + + @Test + public void testResolve() throws Exception { + //falling back on the 'memberOf' attribute + UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(ImmutableSettings.EMPTY); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + assertThat(groups, containsInAnyOrder( + containsString("Avengers"), + containsString("SHIELD"), + containsString("Geniuses"), + containsString("Philanthropists"))); + } + + @Test + public void testResolveCustomGroupAttribute() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("user_group_attribute", "seeAlso") + .build(); + UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + assertThat(groups, hasItem(containsString("Avengers"))); //seeAlso only has Avengers + } + + @Test + public void testResolveInvalidGroupAttribute() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("user_group_attribute", "doesntExist") + .build(); + UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); + List groups = resolver.resolve(ldapContext, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + assertThat(groups, empty()); + } +} \ No newline at end of file