DOMAIN\\username
down-level credentials and this class contains the logic necessary
- * to authenticate this form of a username
+ * Active Directory calls the format DOMAIN\\username
down-level credentials and
+ * this class contains the logic necessary to authenticate this form of a username
*/
static class DownLevelADAuthenticator extends ADAuthenticator {
Cachetrue
if the provide {@link SearchResult} was successfully completed
+ * by the server.
+ * Note: Referrals are not considered a successful response for the
+ * purposes of this method.
+ */
+ private static boolean isSuccess(SearchResult searchResult) {
+ switch (searchResult.getResultCode().intValue()) {
+ case ResultCode.SUCCESS_INT_VALUE:
+ case ResultCode.COMPARE_FALSE_INT_VALUE:
+ case ResultCode.COMPARE_TRUE_INT_VALUE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static SearchResult emptyResult(SearchResult parentResult) {
+ return new SearchResult(
+ parentResult.getMessageID(),
+ ResultCode.SUCCESS,
+ "Empty result",
+ parentResult.getMatchedDN(),
+ null,
+ 0,
+ 0,
+ null
+ );
+ }
+
+ private static LDAPException toException(SearchResult searchResult) {
+ return new LDAPException(
+ searchResult.getResultCode(),
+ searchResult.getDiagnosticMessage(),
+ searchResult.getMatchedDN(),
+ searchResult.getReferralURLs(),
+ searchResult.getResponseControls()
+ );
+ }
+
+ public static Filter createFilter(String filterTemplate, String... arguments)
+ throws LDAPException {
+ return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT)
+ .format((Object[]) encodeFilterValues(arguments), new StringBuffer(), null)
+ .toString());
}
public static String[] attributesToSearchFor(String[] attributes) {
@@ -222,35 +319,41 @@ public final class LdapUtils {
private static class SingleEntryListener extends LdapSearchResultListener {
- SingleEntryListener(LDAPConnection ldapConnection, ActionListener* A standard looking usage pattern could look like this: *
@@ -52,9 +53,14 @@ public abstract class SessionFactory { public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search"; public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification"; public static final String FOLLOW_REFERRALS_SETTING = "follow_referrals"; + public static final SettingIGNORE_REFERRAL_ERRORS_SETTING = Setting.boolSetting( + "ignore_referral_errors", true, Setting.Property.NodeScope); + public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5); - private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE); - private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE); + private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", + Pattern.CASE_INSENSITIVE); + private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", + Pattern.CASE_INSENSITIVE); protected final Logger logger; protected final RealmConfig config; @@ -63,36 +69,43 @@ public abstract class SessionFactory { protected final ServerSet serverSet; protected final boolean sslUsed; + protected final boolean ignoreReferralErrors; protected SessionFactory(RealmConfig config, SSLService sslService) { this.config = config; this.logger = config.logger(getClass()); - TimeValue searchTimeout = config.settings().getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT); + final Settings settings = config.settings(); + TimeValue searchTimeout = settings.getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT); if (searchTimeout.millis() < 1000L) { - logger.warn("ldap_search timeout [{}] is less than the minimum supported search timeout of 1s. using 1s", + logger.warn("ldap_search timeout [{}] is less than the minimum supported search " + + "timeout of 1s. using 1s", searchTimeout.millis()); searchTimeout = TimeValue.timeValueSeconds(1L); } this.timeout = searchTimeout; this.sslService = sslService; - LDAPServers ldapServers = ldapServers(config.settings()); + LDAPServers ldapServers = ldapServers(settings); this.serverSet = serverSet(config, sslService, ldapServers); this.sslUsed = ldapServers.ssl; + this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings); } /** - * Authenticates the given user and opens a new connection that bound to it (meaning, all operations - * under the returned connection will be executed on behalf of the authenticated user. + * Authenticates the given user and opens a new connection that bound to it (meaning, all + * operations under the returned connection will be executed on behalf of the authenticated + * user. * * @param user The name of the user to authenticate the connection with. * @param password The password of the user * @param listener the listener to call on a failure or result */ - public abstract void session(String user, SecuredString password, ActionListener listener); + public abstract void session(String user, SecuredString password, + ActionListener listener); /** - * Returns a flag to indicate if this session factory supports unauthenticated sessions. This means that a session can - * be established without providing any credentials in a call to {@link #unauthenticatedSession(String, ActionListener)} + * Returns a flag to indicate if this session factory supports unauthenticated sessions. + * This means that a session can be established without providing any credentials in a call to + * {@link #unauthenticatedSession(String, ActionListener)} * * @return true if the factory supports unauthenticated sessions */ @@ -110,29 +123,43 @@ public abstract class SessionFactory { throw new UnsupportedOperationException("unauthenticated sessions are not supported"); } - protected static LDAPConnectionOptions connectionOptions(RealmConfig config, SSLService sslService, Logger logger) { + protected static LDAPConnectionOptions connectionOptions(RealmConfig config, + SSLService sslService, Logger logger) { Settings realmSettings = config.settings(); LDAPConnectionOptions options = new LDAPConnectionOptions(); - options.setConnectTimeoutMillis(Math.toIntExact(realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis())); + options.setConnectTimeoutMillis(Math.toIntExact( + realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis() + )); options.setFollowReferrals(realmSettings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true)); - options.setResponseTimeoutMillis(realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()); + options.setResponseTimeoutMillis( + realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis() + ); options.setAllowConcurrentSocketFactoryUse(true); - SSLConfigurationSettings sslConfigurationSettings = SSLConfigurationSettings.withoutPrefix(); + + final SSLConfigurationSettings sslConfigurationSettings = + SSLConfigurationSettings.withoutPrefix(); final Settings realmSSLSettings = realmSettings.getByPrefix("ssl."); - final boolean verificationModeExists = sslConfigurationSettings.verificationMode.exists(realmSSLSettings); - final boolean hostnameVerficationExists = realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null; + final boolean verificationModeExists = + sslConfigurationSettings.verificationMode.exists(realmSSLSettings); + final boolean hostnameVerficationExists = + realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null; + if (verificationModeExists && hostnameVerficationExists) { throw new IllegalArgumentException("[" + HOSTNAME_VERIFICATION_SETTING + "] and [" + - sslConfigurationSettings.verificationMode.getKey() + "] may not be used at the same time"); + sslConfigurationSettings.verificationMode.getKey() + + "] may not be used at the same time"); } else if (verificationModeExists) { - VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings, Settings.EMPTY); + VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings, + Settings.EMPTY); if (verificationMode == VerificationMode.FULL) { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); } } else if (hostnameVerficationExists) { - new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and will be removed in a future version. use " + - "[{}] instead", RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING), - RealmSettings.getFullSettingKey(config, "ssl." + sslConfigurationSettings.verificationMode.getKey())); + new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and " + + "will be removed in a future version. use [{}] instead", + RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING), + RealmSettings.getFullSettingKey(config, "ssl." + + sslConfigurationSettings.verificationMode.getKey())); if (realmSettings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); } @@ -146,7 +173,8 @@ public abstract class SessionFactory { // Parse LDAP urls String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings)); if (ldapUrls == null || ldapUrls.length == 0) { - throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]"); + throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + + "]"); } return new LDAPServers(ldapUrls); } @@ -155,7 +183,8 @@ public abstract class SessionFactory { return null; } - private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService, LDAPServers ldapServers) { + private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService, + LDAPServers ldapServers) { Settings settings = realmConfig.settings(); SocketFactory socketFactory = null; if (ldapServers.ssl()) { @@ -166,8 +195,8 @@ public abstract class SessionFactory { logger.debug("using encryption for LDAP connections without hostname verification"); } } - return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings, socketFactory, - connectionOptions(realmConfig, sslService, logger)); + return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings, + socketFactory, connectionOptions(realmConfig, sslService, logger)); } // package private to use for testing @@ -182,12 +211,19 @@ public abstract class SessionFactory { protected static Set > getSettings() { Set > settings = new HashSet<>(); settings.addAll(LdapLoadBalancing.getSettings()); - settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope)); - settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); - settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); - settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); - settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true, Setting.Property.NodeScope)); - settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true, Setting.Property.NodeScope)); + settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(), + Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT, + Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT, + Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT, + Setting.Property.NodeScope)); + settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true, + Setting.Property.NodeScope)); + settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true, + Setting.Property.NodeScope)); + settings.add(IGNORE_REFERRAL_ERRORS_SETTING); settings.addAll(SSLConfigurationSettings.withPrefix("ssl.").getAllSettings()); return settings; } @@ -208,7 +244,8 @@ public abstract class SessionFactory { addresses[i] = url.getHost(); ports[i] = url.getPort(); } catch (LDAPException e) { - throw new IllegalArgumentException("unable to parse configured LDAP url [" + urls[i] + "]", e); + throw new IllegalArgumentException("unable to parse configured LDAP url [" + + urls[i] + "]", e); } } } @@ -233,13 +270,16 @@ public abstract class SessionFactory { return true; } - final boolean allSecure = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find()); - final boolean allClear = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAP.matcher(s).find()); + final boolean allSecure = Arrays.stream(ldapUrls) + .allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find()); + final boolean allClear = Arrays.stream(ldapUrls) + .allMatch(s -> STARTS_WITH_LDAP.matcher(s).find()); if (!allSecure && !allClear) { //No mixing is allowed because we use the same socketfactory - throw new IllegalArgumentException("configured LDAP protocols are not all equal (ldaps://.. and ldap://..): [" + - Strings.arrayToCommaDelimitedString(ldapUrls) + "]"); + throw new IllegalArgumentException( + "configured LDAP protocols are not all equal (ldaps://.. and ldap://..): [" + + Strings.arrayToCommaDelimitedString(ldapUrls) + "]"); } return allSecure; diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryGroupsResolverTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryGroupsResolverTests.java index 61fe9dcba26..256d474fdc9 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryGroupsResolverTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryGroupsResolverTests.java @@ -24,15 +24,17 @@ import static org.hamcrest.Matchers.is; @Network public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { - private static final String BRUCE_BANNER_DN = "cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; + private static final String BRUCE_BANNER_DN = + "cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; public void testResolveSubTree() throws Exception { Settings settings = Settings.builder() .put("scope", LdapSearchScope.SUB_TREE) .build(); - ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE, null); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, + "DC=ad,DC=test,DC=elasticsearch,DC=com", false); + List groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -48,9 +50,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("scope", LdapSearchScope.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 = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE, null); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, + "DC=ad,DC=test,DC=elasticsearch,DC=com", false); + List groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Users"))); } @@ -59,9 +62,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("scope", LdapSearchScope.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 = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE, null); + ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, + "DC=ad,DC=test,DC=elasticsearch,DC=com", false); + List groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin"))); } @@ -74,7 +78,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { }; final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; PlainActionFuture future = new PlainActionFuture<>(); - ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future); + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, + TimeValue.timeValueSeconds(10), false, future); Filter query = future.actionGet(); assertValidSidQuery(query, expectedSids); } @@ -87,7 +92,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; PlainActionFuture future = new PlainActionFuture<>(); - ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future); + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, + TimeValue.timeValueSeconds(10), false, future); Filter query = future.actionGet(); assertValidSidQuery(query, expectedSids); } @@ -105,7 +111,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { final String dn = BRUCE_BANNER_DN; PlainActionFuture future = new PlainActionFuture<>(); - ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future); + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, + TimeValue.timeValueSeconds(10), false, future); Filter query = future.actionGet(); assertValidSidQuery(query, expectedSids); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactoryTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactoryTests.java index fc7bcf3058e..42756c7e84e 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactoryTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactoryTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.ResultCode; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException; @@ -32,6 +33,8 @@ import static org.hamcrest.Matchers.is; @Network public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryIntegTests { + private final SecuredString SECURED_PASSWORD = SecuredStringTests.build(PASSWORD); + @Override public boolean enableWarningsCheck() { return false; @@ -39,11 +42,14 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI @SuppressWarnings("unchecked") public void testAdAuth() throws Exception { - RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); + RealmConfig config = new RealmConfig("ad-test", + buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), + globalSettings); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, + sslService); String userName = "ironman"; - try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( containsString("Geniuses"), @@ -64,7 +70,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); String userName = "ades\\ironman"; - try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( containsString("Geniuses"), @@ -91,7 +97,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); PlainActionFuture > groups = new PlainActionFuture<>(); - session(sessionFactory, "ironman", SecuredStringTests.build(PASSWORD)).groups(groups); + session(sessionFactory, "ironman", SECURED_PASSWORD).groups(groups); LDAPException expected = expectThrows(LDAPException.class, groups::actionGet); assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } @@ -102,7 +108,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; for(String user: users) { - try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) { assertThat("group avenger test for user "+user, groups(ldap), hasItem(containsString("Avengers"))); } } @@ -116,7 +122,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); String userName = "hulk"; - try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) { List
groups = groups(ldap); assertThat(groups, containsInAnyOrder( @@ -138,7 +144,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); String userName = "hulk"; - try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( @@ -164,7 +170,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); String userName = "hulk"; - try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, hasItem(containsString("Avengers"))); @@ -180,7 +186,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI //Login with the UserPrincipalName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; - try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(ldap.userDn(), is(userDN)); assertThat(groups, containsInAnyOrder( @@ -198,7 +204,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI //login with sAMAccountName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; - try (LdapSession ldap = session(sessionFactory, "selvig", SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, "selvig", SECURED_PASSWORD)) { assertThat(ldap.userDn(), is(userDN)); List groups = groups(ldap); @@ -221,7 +227,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService); //Login with the UserPrincipalName - try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( containsString("CN=Geniuses"), @@ -235,7 +241,13 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI public void testStandardLdapConnection() throws Exception { String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; - Settings settings = LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE); + Settings settings = LdapTestCase.buildLdapSettings( + new String[] { AD_LDAP_URL }, + new String[] { userTemplate }, + groupSearchBase, + LdapSearchScope.SUB_TREE, + null, + true); if (useGlobalSSL == false) { settings = Settings.builder() .put(settings) @@ -247,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService); String user = "Bruce Banner"; - try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( @@ -258,6 +270,42 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI } } + @SuppressWarnings("unchecked") + public void testHandlingLdapReferralErrors() throws Exception { + String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; + String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; + final boolean ignoreReferralErrors = false; + Settings settings = LdapTestCase.buildLdapSettings( + new String[] { AD_LDAP_URL }, + new String[] { userTemplate }, + groupSearchBase, + LdapSearchScope.SUB_TREE, + null, + ignoreReferralErrors); + if (useGlobalSSL == false) { + settings = Settings.builder() + .put(settings) + .put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks")) + .put("ssl.truststore.password", "changeit") + .build(); + } + RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService); + + String user = "Bruce Banner"; + try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) { + final UncategorizedExecutionException exception = expectThrows( + UncategorizedExecutionException.class, + () -> groups(ldap) + ); + final Throwable cause = exception.getCause(); + assertThat(cause, instanceOf(ExecutionException.class)); + assertThat(cause.getCause(), instanceOf(LDAPException.class)); + final LDAPException ldapException = (LDAPException) cause.getCause(); + assertThat(ldapException.getResultCode(), is(ResultCode.INVALID_CREDENTIALS)); + } + } + @SuppressWarnings("unchecked") public void testStandardLdapWithAttributeGroups() throws Exception { String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -273,7 +321,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService); String user = "Bruce Banner"; - try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) { + try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) { List groups = groups(ldap); assertThat(groups, containsInAnyOrder( @@ -290,7 +338,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI String userName = "ironman"; UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class, - () -> session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))); + () -> session(sessionFactory, userName, SECURED_PASSWORD)); assertThat(e.getCause(), instanceOf(ExecutionException.class)); assertThat(e.getCause().getCause(), instanceOf(LDAPException.class)); final LDAPException expected = (LDAPException) e.getCause().getCause(); @@ -309,7 +357,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI String user = "Bruce Banner"; UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class, - () -> session(sessionFactory, user, SecuredStringTests.build(PASSWORD))); + () -> session(sessionFactory, user, SECURED_PASSWORD)); assertThat(e.getCause(), instanceOf(ExecutionException.class)); assertThat(e.getCause().getCause(), instanceOf(LDAPException.class)); final LDAPException expected = (LDAPException) e.getCause().getCause(); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java index 624f1b9f2bd..e750374ec46 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java @@ -21,6 +21,7 @@ import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase; +import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.SecuredStringTests; import org.elasticsearch.xpack.ssl.SSLService; @@ -307,8 +308,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; Settings settings = Settings.builder() - .put(LdapTestCase.buildLdapSettings(new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL }, Strings.EMPTY_ARRAY, - groupSearchBase, LdapSearchScope.SUB_TREE)) + .put(LdapTestCase.buildLdapSettings( + new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL }, + Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE, null, + true)) .put("user_search.base_dn", userSearchBase) .put("bind_dn", "ironman@ad.test.elasticsearch.com") .put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD) diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverInMemoryTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverInMemoryTests.java new file mode 100644 index 00000000000..b237bbd985f --- /dev/null +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverInMemoryTests.java @@ -0,0 +1,64 @@ +/* + * 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.xpack.security.authc.ldap; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPConnectionOptions; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPURL; +import com.unboundid.ldap.sdk.ResultCode; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; +import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase; +import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class SearchGroupsResolverInMemoryTests extends LdapTestCase { + + /** + * Tests that a client-side timeout in the asynchronous LDAP SDK is treated as a failure, rather + * than simply returning no results. + */ + public void testSearchTimeoutIsFailure() throws Exception { + + ldapServers[0].setProcessingDelayMillis(100); + + final LDAPConnectionOptions options = new LDAPConnectionOptions(); + options.setConnectTimeoutMillis(500); + options.setResponseTimeoutMillis(5); + + final LDAPURL ldapurl = new LDAPURL(ldapUrls()[0]); + final LDAPConnection connection = LdapUtils.privilegedConnect( + () -> new LDAPConnection(options, + ldapurl.getHost(), ldapurl.getPort()) + ); + + final Settings settings = Settings.builder() + .put("group_search.base_dn", "ou=groups,o=sevenSeas") + .put("group_search.scope", LdapSearchScope.SUB_TREE) + .build(); + final SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + final PlainActionFuture > future = new PlainActionFuture<>(); + resolver.resolve(connection, + "cn=William Bush,ou=people,o=sevenSeas", + TimeValue.timeValueSeconds(30), + logger, + null, future); + + final ExecutionException exception = expectThrows(ExecutionException.class, future::get); + final Throwable cause = exception.getCause(); + assertThat(cause, instanceOf(LDAPException.class)); + assertThat(((LDAPException) cause).getResultCode(), is(ResultCode.TIMEOUT)); + } + +} \ No newline at end of file diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java index e19d491808c..d98185af36d 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java @@ -25,7 +25,6 @@ import org.junit.Before; import org.junit.BeforeClass; import java.security.AccessController; -import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; @@ -92,17 +91,27 @@ public abstract class LdapTestCase extends ESTestCase { return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, null); } - public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope, + public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, + String groupSearchBase, LdapSearchScope scope, LdapLoadBalancing serverSetType) { + return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, + serverSetType, false); + } + + public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, + String groupSearchBase, LdapSearchScope scope, + LdapLoadBalancing serverSetType, + boolean ignoreReferralErrors) { Settings.Builder builder = Settings.builder() .putArray(URLS_SETTING, ldapUrl) .putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate) + .put(SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING.getKey(), ignoreReferralErrors) .put("group_search.base_dn", groupSearchBase) .put("group_search.scope", scope) .put("ssl.verification_mode", VerificationMode.CERTIFICATE); if (serverSetType != null) { - builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, - serverSetType.toString()); + builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, serverSetType.toString()); } return builder.build(); }