From 59fcb205b59adf96b31fb279f6a91dc28e7d8b5c Mon Sep 17 00:00:00 2001 From: jaymode Date: Thu, 16 Jun 2016 14:34:04 -0400 Subject: [PATCH] security: active directory and ldap realm improvements This commit is a combination of enhancements and fixes to the active directory and ldap realms. The active directory realm has been enhanced to add support for authentication against multiple domains in a forest. The ldap realm has been updated so that: * attributes required for group resolution are loaded eagerly if possible * user search can now be executed using unpooled connections * the default search filter for groups now includes posixGroup and memberUid to avoid users needed to understand ldap filters Finally, the UnboundID LDAP SDK was upgraded to the latest version and some long standing AwaitsFix were addressed. Closes elastic/elasticsearch#20 Closes elastic/elasticsearch#26 Closes elastic/elasticsearch#1950 Closes elastic/elasticsearch#2145 Closes elastic/elasticsearch#2363 Original commit: elastic/x-pack-elasticsearch@63c9be2337032174e1f1f7f8a517719b1718b8bc --- elasticsearch/x-pack/build.gradle | 2 +- .../ActiveDirectoryGroupsResolver.java | 26 +- .../activedirectory/ActiveDirectoryRealm.java | 2 +- .../ActiveDirectorySessionFactory.java | 261 +++++++++++++++--- .../xpack/security/authc/ldap/LdapRealm.java | 10 +- .../authc/ldap/LdapSessionFactory.java | 24 +- .../ldap/LdapUserSearchSessionFactory.java | 178 ++++++------ .../authc/ldap/SearchGroupsResolver.java | 92 +++--- .../ldap/UserAttributeGroupsResolver.java | 50 ++-- .../authc/ldap/support/AbstractLdapRealm.java | 3 +- .../authc/ldap/support/LdapSession.java | 16 +- .../authc/ldap/support/LdapUtils.java | 8 +- .../authc/ldap/support/SessionFactory.java | 28 +- .../ActiveDirectoryGroupsResolverTests.java | 26 +- .../ActiveDirectoryRealmTests.java | 14 +- .../ActiveDirectoryRealmUsageTests.java | 2 +- .../ActiveDirectorySessionFactoryTests.java | 79 +++--- .../authc/ldap/GroupsResolverTestCase.java | 1 - .../security/authc/ldap/LdapRealmTests.java | 14 +- .../authc/ldap/LdapSessionFactoryTests.java | 45 ++- .../LdapUserSearchSessionFactoryTests.java | 101 +++---- .../security/authc/ldap/OpenLdapTests.java | 35 +-- .../authc/ldap/SearchGroupsResolverTests.java | 28 +- .../UserAttributeGroupsResolverTests.java | 6 +- .../SessionFactoryLoadBalancingTests.java | 2 +- .../ldap/support/SessionFactoryTests.java | 2 +- .../netty3/HandshakeWaitingHandlerTests.java | 11 +- 27 files changed, 660 insertions(+), 406 deletions(-) diff --git a/elasticsearch/x-pack/build.gradle b/elasticsearch/x-pack/build.gradle index 0a96f2c27ab..a6ea623e29d 100644 --- a/elasticsearch/x-pack/build.gradle +++ b/elasticsearch/x-pack/build.gradle @@ -29,7 +29,7 @@ dependencies { // security deps compile project(path: ':modules:transport-netty3', configuration: 'runtime') compile 'dk.brics.automaton:automaton:1.11-8' - compile 'com.unboundid:unboundid-ldapsdk:2.3.8' + compile 'com.unboundid:unboundid-ldapsdk:3.1.1' compile 'org.bouncycastle:bcprov-jdk15on:1.54' compile 'org.bouncycastle:bcpkix-jdk15on:1.54' testCompile 'com.google.jimfs:jimfs:1.1' diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java index 812a16ece33..8fff603b1b8 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java @@ -13,15 +13,15 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; 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.LdapSession.GroupsResolver; -import org.elasticsearch.xpack.security.support.Exceptions; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; @@ -41,9 +41,14 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE); } - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { + @Override + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) { Filter groupSearchFilter = buildGroupQuery(connection, userDn, timeout, logger); logger.debug("group SID to DN search filter: [{}]", groupSearchFilter); + if (groupSearchFilter == null) { + return Collections.emptyList(); + } SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), groupSearchFilter, SearchRequest.NO_ATTRIBUTES); searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); @@ -51,7 +56,8 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { try { results = search(connection, searchRequest, logger); } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to fetch AD groups for DN [{}]", e, userDn); + logger.error("failed to fetch AD groups for DN [{}]", e, userDn); + return Collections.emptyList(); } List groupList = new ArrayList<>(); @@ -64,11 +70,20 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { return groupList; } + @Override + public String[] attributes() { + // we have to return null since the tokenGroups attribute is computed and can only be retrieved using a BASE level search + return null; + } + static Filter buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { try { SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, "tokenGroups"); request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); SearchResultEntry entry = searchForEntry(connection, request, logger); + if (entry == null) { + return null; + } Attribute attribute = entry.getAttribute("tokenGroups"); byte[][] tokenGroupSIDBytes = attribute.getValueByteArrays(); List orFilters = new ArrayList<>(tokenGroupSIDBytes.length); @@ -77,7 +92,8 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { } return Filter.createORFilter(orFilters); } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to fetch AD groups for DN [{}]", e, userDn); + logger.error("failed to fetch AD groups for DN [{}]", e, userDn); + return null; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java index 1596ba47d53..144b3c72f21 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java @@ -41,7 +41,7 @@ public class ActiveDirectoryRealm extends AbstractLdapRealm { @Override public ActiveDirectoryRealm create(RealmConfig config) { - ActiveDirectorySessionFactory connectionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory connectionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); return new ActiveDirectoryRealm(config, connectionFactory, roleMapper); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java index 365724ee2f0..b0231319eb9 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java @@ -5,12 +5,18 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; +import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPConnectionOptions; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; -import org.elasticsearch.common.Strings; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; 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; @@ -19,11 +25,11 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import java.io.IOException; +import java.util.concurrent.ExecutionException; +import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search; -import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; /** * This Class creates LdapSessions authenticating via the custom Active Directory protocol. (that being @@ -40,12 +46,13 @@ public class ActiveDirectorySessionFactory extends SessionFactory { public static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search.base_dn"; public static final String AD_USER_SEARCH_FILTER_SETTING = "user_search.filter"; public static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope"; + private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})"; - private final String userSearchDN; private final String domainName; - private final String userSearchFilter; - private final LdapSearchScope userSearchScope; private final GroupsResolver groupResolver; + private final DefaultADAuthenticator defaultADAuthenticator; + private final DownLevelADAuthenticator downLevelADAuthenticator; + private final UpnADAuthenticator upnADAuthenticator; public ActiveDirectorySessionFactory(RealmConfig config, ClientSSLService sslService) { super(config, sslService); @@ -55,63 +62,227 @@ public class ActiveDirectorySessionFactory extends SessionFactory { throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory"); } String domainDN = buildDnFromDomain(domainName); - userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); - userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); - userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" + - "(userPrincipalName={0}@" + domainName + ")))"); groupResolver = new ActiveDirectoryGroupsResolver(settings.getAsSettings("group_search"), domainDN); + defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout, logger, groupResolver, domainDN); + downLevelADAuthenticator = new DownLevelADAuthenticator(settings, timeout, logger, groupResolver, domainDN); + upnADAuthenticator = new UpnADAuthenticator(settings, timeout, logger, groupResolver, domainDN); } - @Override - protected LDAPServers ldapServers(Settings settings) { - String[] ldapUrls = settings.getAsArray(URLS_SETTING, new String[]{"ldap://" + domainName + ":389"}); - return new LDAPServers(ldapUrls); + protected String[] getDefaultLdapUrls(Settings settings) { + return new String[] {"ldap://" + settings.get(AD_DOMAIN_NAME_SETTING) + ":389"}; } /** * This is an active directory bind that looks up the user DN after binding with a windows principal. * - * @param userName name of the windows user without the domain - * @return An authenticated + * @param username name of the windows user without the domain + * @return An authenticated LdapSession */ @Override - protected LdapSession getSession(String userName, SecuredString password) throws Exception { - LDAPConnection connection; - - try { - connection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("failed to connect to any active directory servers", e); - } - - String userPrincipal = userName + "@" + domainName; - try { - connection.bind(userPrincipal, new String(password.internalChars())); - SearchRequest searchRequest = new SearchRequest(userSearchDN, userSearchScope.scope(), - createFilter(userSearchFilter, userName), SearchRequest.NO_ATTRIBUTES); - searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResult results = search(connection, searchRequest, logger); - int numResults = results.getEntryCount(); - if (numResults > 1) { - throw new IllegalStateException("search for user [" + userName + "] by principle name yielded multiple results"); - } else if (numResults < 1) { - throw new IllegalStateException("search for user [" + userName + "] by principle name yielded no results"); - } - String dn = results.getSearchEntries().get(0).getDN(); - return new LdapSession(connectionLogger, connection, dn, groupResolver, timeout); - } catch (LDAPException e) { - connection.close(); - throw authenticationError("unable to authenticate user [{}] to active directory domain [{}]", e, userName, domainName); - } + protected LdapSession getSession(String username, SecuredString password) throws Exception { + LDAPConnection connection = serverSet.getConnection(); + ADAuthenticator authenticator = getADAuthenticator(username); + return authenticator.authenticate(connection, username, password); } /** * @param domain active directory domain name * @return LDAP DN, distinguished name, of the root of the domain */ - String buildDnFromDomain(String domain) { + static String buildDnFromDomain(String domain) { return "DC=" + domain.replace(".", ",DC="); } + ADAuthenticator getADAuthenticator(String username) { + if (username.indexOf('\\') > 0) { + return downLevelADAuthenticator; + } else if (username.indexOf("@") > 0) { + return upnADAuthenticator; + } + return defaultADAuthenticator; + } + + abstract static class ADAuthenticator { + + final TimeValue timeout; + final ESLogger logger; + final GroupsResolver groupsResolver; + final String userSearchDN; + final LdapSearchScope userSearchScope; + + ADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + this.timeout = timeout; + this.logger = logger; + this.groupsResolver = groupsResolver; + userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); + userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); + } + + LdapSession authenticate(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + boolean success = false; + try { + connection.bind(bindUsername(username), new String(password.internalChars())); + SearchRequest searchRequest = getSearchRequest(connection, username, password); + searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResult results = search(connection, searchRequest, logger); + int numResults = results.getEntryCount(); + if (numResults > 1) { + throw new IllegalStateException("search for user [" + username + "] by principle name yielded multiple results"); + } else if (numResults < 1) { + throw new IllegalStateException("search for user [" + username + "] by principle name yielded no results"); + } + + String dn = results.getSearchEntries().get(0).getDN(); + LdapSession session = new LdapSession(logger, connection, dn, groupsResolver, timeout, null); + success = true; + return session; + } finally { + if (success == false) { + connection.close(); + } + } + } + + String bindUsername(String username) { + return username; + } + + abstract SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException; + } + + /** + * This authenticator is used for usernames that do not contain an `@` or `/`. It attempts a bind with the provided username combined + * with the domain name specified in settings. On AD DS this will work for both upn@domain and samaccountname@domain; AD LDS will only + * support the upn format + */ + static class DefaultADAuthenticator extends ADAuthenticator { + + final String userSearchFilter; + + final String domainName; + DefaultADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + domainName = settings.get(AD_DOMAIN_NAME_SETTING); + userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" + + "(userPrincipalName={0}@" + domainName + ")))"); + } + + @Override + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + return new SearchRequest(userSearchDN, userSearchScope.scope(), + createFilter(userSearchFilter, username), attributesToSearchFor(groupsResolver.attributes())); + } + + @Override + String bindUsername(String username) { + return username + "@" + domainName; + } + } + + /** + * 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 { + Cache domainNameCache = CacheBuilder.builder().setMaximumWeight(100).build(); + + final String domainDN; + final Settings settings; + + DownLevelADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + this.domainDN = domainDN; + this.settings = settings; + } + + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + String[] parts = username.split("\\\\"); + assert parts.length == 2; + final String netBiosDomainName = parts[0]; + final String accountName = parts[1]; + + final String domainDn = netBiosDomainNameToDn(connection, netBiosDomainName, username, password); + + return new SearchRequest(domainDn, LdapSearchScope.SUB_TREE.scope(), + createFilter("(&(objectClass=user)(sAMAccountName={0}))", accountName), + attributesToSearchFor(groupsResolver.attributes())); + } + + String netBiosDomainNameToDn(LDAPConnection connection, String netBiosDomainName, String username, SecuredString password) + throws LDAPException { + try { + return domainNameCache.computeIfAbsent(netBiosDomainName, (key) -> { + LDAPConnection searchConnection = connection; + boolean openedConnection = false; + try { + // global catalog does not replicate the necessary information by default + // TODO add settings for ports and maybe cache connectionOptions + if (usingGlobalCatalog(settings, connection)) { + LDAPConnectionOptions options = connectionOptions(settings); + if (connection.getSSLSession() != null) { + searchConnection = new LDAPConnection(connection.getSocketFactory(), options, + connection.getConnectedAddress(), 636); + } else { + searchConnection = new LDAPConnection(options, connection.getConnectedAddress(), 389); + } + openedConnection = true; + searchConnection.bind(username, new String(password.internalChars())); + } + + SearchRequest searchRequest = new SearchRequest(domainDN, LdapSearchScope.SUB_TREE.scope(), + createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName), "ncname"); + SearchResult results = search(searchConnection, searchRequest, logger); + if (results.getEntryCount() > 0) { + Attribute attribute = results.getSearchEntries().get(0).getAttribute("ncname"); + if (attribute != null) { + return attribute.getValue(); + } + } + logger.debug("failed to find domain name DN from netbios name [{}]", netBiosDomainName); + return null; + } finally { + if (openedConnection) { + searchConnection.close(); + } + } + }); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof LDAPException) { + throw (LDAPException) cause; + } else { + connection.close(); + throw new ElasticsearchException("error occurred while mapping [{}] to domain DN", cause, netBiosDomainName); + } + } + } + + static boolean usingGlobalCatalog(Settings settings, LDAPConnection ldapConnection) { + Boolean usingGlobalCatalog = settings.getAsBoolean("global_catalog", null); + if (usingGlobalCatalog != null) { + return usingGlobalCatalog; + } + return ldapConnection.getConnectedPort() == 3268 || ldapConnection.getConnectedPort() == 3269; + } + } + + static class UpnADAuthenticator extends ADAuthenticator { + + private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))"; + + UpnADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + } + + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + String[] parts = username.split("@"); + assert parts.length == 2; + final String accountName = parts[0]; + final String domainName = parts[1]; + final String domainDN = buildDnFromDomain(domainName); + return new SearchRequest(domainDN, LdapSearchScope.SUB_TREE.scope(), + createFilter(UPN_USER_FILTER, accountName, username), attributesToSearchFor(groupsResolver.attributes())); + } + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index cc1a739f36d..b846c4019ef 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -16,7 +17,6 @@ import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.watcher.ResourceWatcherService; -import java.io.IOException; import java.util.Map; /** @@ -55,12 +55,12 @@ public class LdapRealm extends AbstractLdapRealm { SessionFactory sessionFactory = sessionFactory(config, clientSSLService); DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); return new LdapRealm(config, sessionFactory, roleMapper); - } catch (IOException e) { + } catch (LDAPException e) { throw new ElasticsearchException("failed to create realm [{}/{}]", e, LdapRealm.TYPE, config.name()); } } - static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws IOException { + static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws LDAPException { Settings searchSettings = userSearchSettings(config); if (!searchSettings.names().isEmpty()) { if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { @@ -68,9 +68,9 @@ public class LdapRealm extends AbstractLdapRealm { "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + "authentication section of the X-Pack guide."); } - return new LdapUserSearchSessionFactory(config, clientSSLService).init(); + return new LdapUserSearchSessionFactory(config, clientSSLService); } - return new LdapSessionFactory(config, clientSSLService).init(); + return new LdapSessionFactory(config, clientSSLService); } static Settings userSearchSettings(RealmConfig config) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java index f54c4399609..e7c2d99b71c 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java @@ -54,13 +54,7 @@ public class LdapSessionFactory extends SessionFactory { */ @Override protected LdapSession getSession(String username, SecuredString password) throws Exception { - LDAPConnection connection; - - try { - connection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("failed to connect to any LDAP servers", e); - } + LDAPConnection connection = serverSet.getConnection(); LDAPException lastException = null; String passwordString = new String(password.internalChars()); @@ -68,20 +62,22 @@ public class LdapSessionFactory extends SessionFactory { String dn = buildDnFromTemplate(username, template); try { connection.bind(dn, passwordString); - return new LdapSession(connectionLogger, connection, dn, groupResolver, timeout); + return new LdapSession(logger, connection, dn, groupResolver, timeout, null); } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + // we catch the ldapException here since we expect it can happen and we shouldn't be logging this all the time otherwise + // it is just noise + logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + if (lastException == null) { + lastException = e; } else { - logger.warn("failed LDAP authentication with user template [{}] and DN [{}]: {}", template, dn, e.getMessage()); + lastException.addSuppressed(e); } - - lastException = e; } } connection.close(); - throw Exceptions.authenticationError("failed LDAP authentication", lastException); + assert lastException != null; + throw lastException; } /** diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java index fd84514a547..77ceefcc7e3 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java @@ -8,12 +8,13 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnectionPool; +import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.ldap.sdk.SimpleBindRequest; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; @@ -26,28 +27,29 @@ import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.support.Exceptions; -import java.io.IOException; import java.util.Locale; import static com.unboundid.ldap.sdk.Filter.createEqualityFilter; import static com.unboundid.ldap.sdk.Filter.encodeValue; +import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; -public class LdapUserSearchSessionFactory extends SessionFactory { +class LdapUserSearchSessionFactory extends SessionFactory { static final int DEFAULT_CONNECTION_POOL_SIZE = 20; - static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 5; + static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; static final String DEFAULT_USERNAME_ATTRIBUTE = "uid"; static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L); private final String userSearchBaseDn; private final LdapSearchScope scope; private final String userAttribute; + private final GroupsResolver groupResolver; + private final boolean useConnectionPool; - private LDAPConnectionPool connectionPool; - private GroupsResolver groupResolver; + private final LDAPConnectionPool connectionPool; - public LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) { + LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) throws LDAPException { super(config, sslService); Settings settings = config.settings(); userSearchBaseDn = settings.get("user_search.base_dn"); @@ -56,63 +58,52 @@ public class LdapUserSearchSessionFactory extends SessionFactory { } scope = LdapSearchScope.resolve(settings.get("user_search.scope"), LdapSearchScope.SUB_TREE); userAttribute = settings.get("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE); - } - - @Override - public LdapUserSearchSessionFactory init() { - super.init(); - connectionPool = createConnectionPool(config, serverSet, timeout, logger); groupResolver = groupResolver(config.settings()); - return this; - } - - private synchronized LDAPConnectionPool connectionPool() throws IOException { - if (connectionPool == null) { + useConnectionPool = settings.getAsBoolean("user_search.pool.enabled", true); + if (useConnectionPool) { connectionPool = createConnectionPool(config, serverSet, timeout, logger); - // if it is still null throw an exception - if (connectionPool == null) { - String msg = "failed to create a connection pool for realm [" + config.name() + "] as no LDAP servers are available"; - throw new IOException(msg); - } + } else { + connectionPool = null; } - - return connectionPool; } - static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) { + static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) + throws LDAPException { Settings settings = config.settings(); SimpleBindRequest bindRequest = bindRequest(settings); - int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); - int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + final int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); + final int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + LDAPConnectionPool pool = null; + boolean success = false; try { - LDAPConnectionPool pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); + pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); pool.setRetryFailedOperationsDueToInvalidConnections(true); if (settings.getAsBoolean("user_search.pool.health_check.enabled", true)) { String entryDn = settings.get("user_search.pool.health_check.dn", (bindRequest == null) ? null : bindRequest.getBindDN()); - if (entryDn == null) { - pool.close(); - throw new IllegalArgumentException("[bind_dn] has not been specified so a value must be specified for [user_search" + - ".pool.health_check.dn] or [user_search.pool.health_check.enabled] must be set to false"); + final long healthCheckInterval = + settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL).millis(); + if (entryDn != null) { + // Checks the status of the LDAP connection at a specified interval in the background. We do not check on + // on create as the LDAP server may require authentication to get an entry and a bind request has not been executed + // yet so we could end up never getting a connection. We do not check on checkout as we always set retry operations + // and the pool will handle a bad connection without the added latency on every operation + LDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), + false, false, false, true, false); + pool.setHealthCheck(healthCheck); + pool.setHealthCheckIntervalMillis(healthCheckInterval); + } else { + logger.warn("[bind_dn] and [user_search.pool.health_check.dn] have not been specified so no " + + "ldap query will be run as a health check"); } - long healthCheckInterval = settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL) - .millis(); - // Checks the status of the LDAP connection at a specified interval in the background. We do not check on - // on create as the LDAP server may require authentication to get an entry. We do not check on checkout - // as we always set retry operations and the pool will handle a bad connection without the added latency on every operation - GetEntryLDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), - false, false, false, true, false); - pool.setHealthCheck(healthCheck); - pool.setHealthCheckIntervalMillis(healthCheckInterval); } + + success = true; return pool; - } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("unable to create connection pool for realm [{}]", e, config.name()); - } else { - logger.error("unable to create connection pool for realm [{}]: {}", config.name(), e.getMessage()); + } finally { + if (success == false && pool != null) { + pool.close(); } } - return null; } static SimpleBindRequest bindRequest(Settings settings) { @@ -126,12 +117,38 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override protected LdapSession getSession(String user, SecuredString password) throws Exception { + if (useConnectionPool) { + return getSessionWithPool(user, password); + } else { + return getSessionWithoutPool(user, password); + } + } + + private LdapSession getSessionWithPool(String user, SecuredString password) throws Exception { + SearchResultEntry searchResult = findUser(user, connectionPool); + assert searchResult != null; + final String dn = searchResult.getDN(); + connectionPool.bindAndRevertAuthentication(dn, new String(password.internalChars())); + return new LdapSession(logger, connectionPool, dn, groupResolver, timeout, searchResult.getAttributes()); + } + + private LdapSession getSessionWithoutPool(String user, SecuredString password) throws Exception { + boolean success = false; + LDAPConnection connection = null; try { - String dn = findUserDN(user); - tryBind(dn, password); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to authenticate user [{}]", e, user); + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + SearchResultEntry searchResult = findUser(user, connection); + assert searchResult != null; + final String dn = searchResult.getDN(); + connection.bind(dn, new String(password.internalChars())); + LdapSession session = new LdapSession(logger, connection, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } @@ -142,46 +159,45 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override public LdapSession unauthenticatedSession(String user) throws Exception { + LDAPConnection connection = null; + boolean success = false; try { - String dn = findUserDN(user); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to lookup user [{}]", e, user); + final LDAPInterface ldapInterface; + if (useConnectionPool) { + ldapInterface = connectionPool; + } else { + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + ldapInterface = connection; + } + + SearchResultEntry searchResult = findUser(user, ldapInterface); + assert searchResult != null; + final String dn = searchResult.getDN(); + LdapSession session = new LdapSession(logger, ldapInterface, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } - private String findUserDN(String user) throws Exception { - SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)) - , SearchRequest.NO_ATTRIBUTES); + private SearchResultEntry findUser(String user, LDAPInterface ldapInterface) throws Exception { + SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), + attributesToSearchFor(groupResolver.attributes())); request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - LDAPConnectionPool connectionPool = connectionPool(); - SearchResultEntry entry = searchForEntry(connectionPool, request, logger); + SearchResultEntry entry = searchForEntry(ldapInterface, request, logger); if (entry == null) { throw Exceptions.authenticationError("failed to find user [{}] with search base [{}] scope [{}]", user, userSearchBaseDn, scope.toString().toLowerCase(Locale.ENGLISH)); } - return entry.getDN(); - } - - private void tryBind(String dn, SecuredString password) throws IOException { - LDAPConnection bindConnection; - try { - bindConnection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("unable to connect to any LDAP servers for bind", e); - } - - try { - bindConnection.bind(dn, new String(password.internalChars())); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed LDAP authentication for DN [{}]", e, dn); - } finally { - bindConnection.close(); - } + return entry; } /* - * This method is used to cleanup the connections for tests + * This method is used to cleanup the connections */ void shutdown() { if (connectionPool != null) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java index 12600548baf..52d668ec086 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java @@ -12,15 +12,15 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; 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.LdapSession.GroupsResolver; -import org.elasticsearch.xpack.security.support.Exceptions; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; @@ -29,20 +29,21 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* -*/ + * Resolves the groups for a user by executing a search with a filter usually that contains a group object class with a attribute that + * matches an ID of the user + */ class SearchGroupsResolver implements GroupsResolver { private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" + - "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group))" + - "(|(uniqueMember={0})(member={0})))"; + "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" + + "(|(uniqueMember={0})(member={0})(memberUid={0})))"; private final String baseDn; private final String filter; private final String userAttribute; private final LdapSearchScope scope; - public SearchGroupsResolver(Settings settings) { + SearchGroupsResolver(Settings settings) { baseDn = settings.get("base_dn"); if (baseDn == null) { throw new IllegalArgumentException("base_dn must be specified"); @@ -53,37 +54,60 @@ class SearchGroupsResolver implements GroupsResolver { } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - List groups = new LinkedList<>(); - - String userId = userAttribute != null ? readUserAttribute(connection, userDn, timeout, logger) : userDn; - try { - SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), - SearchRequest.NO_ATTRIBUTES); - searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResult results = search(connection, searchRequest, logger); - for (SearchResultEntry entry : results.getSearchEntries()) { - groups.add(entry.getDN()); - } - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not search for LDAP groups for DN [{}]", e, userDn); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + String userId = getUserId(userDn, attributes, connection, timeout, logger); + if (userId == null) { + // attributes were queried but the requested wasn't found + return Collections.emptyList(); } + SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), + SearchRequest.NO_ATTRIBUTES); + searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResult results = search(connection, searchRequest, logger); + List groups = new ArrayList<>(results.getSearchEntries().size()); + for (SearchResultEntry entry : results.getSearchEntries()) { + groups.add(entry.getDN()); + } return groups; } - String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry results = searchForEntry(connection, request, logger); - Attribute attribute = results.getAttribute(userAttribute); - if (attribute == null) { - throw Exceptions.authenticationError("no results returned for DN [{}] attribute [{}]", userDn, userAttribute); - } - return attribute.getValue(); - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not retrieve attribute [{}] for DN [{}]", e, userAttribute, userDn); + public String[] attributes() { + if (userAttribute != null) { + return new String[] { userAttribute }; } + return null; + } + + private String getUserId(String dn, Collection attributes, LDAPInterface connection, TimeValue + timeout, ESLogger logger) throws LDAPException { + if (userAttribute == null) { + return dn; + } + + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(userAttribute)) { + return attribute.getValue(); + } + } + } + + return readUserAttribute(connection, dn, timeout, logger); + } + + String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) throws LDAPException { + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry results = searchForEntry(connection, request, logger); + if (results == null) { + return null; + } + Attribute attribute = results.getAttribute(userAttribute); + if (attribute == null) { + return null; + } + return attribute.getValue(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java index 9ae30969696..e04d9924063 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java @@ -11,48 +11,64 @@ import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* +* Resolves the groups of a user based on the value of a attribute of the user's ldap entry */ class UserAttributeGroupsResolver implements GroupsResolver { private final String attribute; - public UserAttributeGroupsResolver(Settings settings) { + UserAttributeGroupsResolver(Settings settings) { this(settings.get("user_group_attribute", "memberOf")); } - public UserAttributeGroupsResolver(String attribute) { - this.attribute = attribute; + private UserAttributeGroupsResolver(String attribute) { + this.attribute = Objects.requireNonNull(attribute); } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry result = searchForEntry(connection, request, logger); - Attribute attributeReturned = result.getAttribute(attribute); - if (attributeReturned == null) { - return Collections.emptyList(); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(attribute)) { + String[] values = attribute.getValues(); + return Arrays.asList(values); + } } - String[] values = attributeReturned.getValues(); - return Arrays.asList(values); - } catch (LDAPException e) { - throw new ElasticsearchException("could not look up group attributes for DN [{}]", e, userDn); + return Collections.emptyList(); } + + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry result = searchForEntry(connection, request, logger); + if (result == null) { + return Collections.emptyList(); + } + Attribute attributeReturned = result.getAttribute(attribute); + if (attributeReturned == null) { + return Collections.emptyList(); + } + String[] values = attributeReturned.getValues(); + return Arrays.asList(values); + } + + @Override + public String[] attributes() { + return new String[] { attribute }; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java index 68ef7ea0e7f..f8a32d231a7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.rest.RestController; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.RealmConfig; @@ -88,7 +89,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm { } } - private User createUser(String principal, LdapSession session) { + private User createUser(String principal, LdapSession session) throws LDAPException { List groupDNs = session.groups(); Set roles = roleMapper.resolveRoles(session.userDn(), groupDNs); return new User(principal, roles.toArray(new String[roles.size()])); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java index f6896d8498a..a41e28e7e86 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java @@ -5,12 +5,15 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; +import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPInterface; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.unit.TimeValue; import java.io.Closeable; +import java.util.Collection; import java.util.List; /** @@ -23,6 +26,7 @@ public class LdapSession implements Closeable { protected final String userDn; protected final GroupsResolver groupsResolver; protected final TimeValue timeout; + protected final Collection attributes; /** * This object is intended to be constructed by the LdapConnectionFactory @@ -32,12 +36,14 @@ public class LdapSession implements Closeable { * outside of and be reused across all connections. We can't keep a static logger in this class * since we want the logger to be contextual (i.e. aware of the settings and its environment). */ - public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout) { + public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout, + Collection attributes) { this.logger = logger; this.ldap = connection; this.userDn = userDn; this.groupsResolver = groupsResolver; this.timeout = timeout; + this.attributes = attributes; } /** @@ -61,13 +67,15 @@ public class LdapSession implements Closeable { /** * @return List of fully distinguished group names */ - public List groups() { - return groupsResolver.resolve(ldap, userDn, timeout, logger); + public List groups() throws LDAPException { + return groupsResolver.resolve(ldap, userDn, timeout, logger, attributes); } public interface GroupsResolver { - List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger); + List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException; + String[] attributes(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java index a66964e4e76..f2ccb1b4fbb 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java @@ -46,7 +46,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.search(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResult search(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResult results; @@ -68,7 +68,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.searchForEntry(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResultEntry searchForEntry(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResultEntry entry; @@ -93,6 +93,10 @@ public final class LdapUtils { new StringBuffer(), null).toString()); } + public static String[] attributesToSearchFor(String[] attributes) { + return attributes == null ? new String[] { SearchRequest.NO_ATTRIBUTES } : attributes; + } + static String[] encodeFilterValues(String... arguments) { for (int i = 0; i < arguments.length; i++) { arguments[i] = Filter.encodeValue(arguments[i]); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index e707247cbc1..cd8799486b7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -48,18 +48,16 @@ public abstract class SessionFactory { private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE); protected final ESLogger logger; - protected final ESLogger connectionLogger; protected final RealmConfig config; protected final TimeValue timeout; protected final ClientSSLService sslService; - protected ServerSet serverSet; - protected boolean sslUsed; + protected final ServerSet serverSet; + protected final boolean sslUsed; protected SessionFactory(RealmConfig config, ClientSSLService sslService) { this.config = config; this.logger = config.logger(getClass()); - this.connectionLogger = config.logger(getClass()); TimeValue searchTimeout = config.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", @@ -68,6 +66,9 @@ public abstract class SessionFactory { } this.timeout = searchTimeout; this.sslService = sslService; + LDAPServers ldapServers = ldapServers(config.settings()); + this.serverSet = serverSet(config.settings(), sslService, ldapServers); + this.sslUsed = ldapServers.ssl; } /** @@ -80,9 +81,6 @@ public abstract class SessionFactory { * @throws Exception if an error occurred when creating the session */ public final LdapSession session(String user, SecuredString password) throws Exception { - if (serverSet == null) { - throw new IllegalStateException("session factory is not initialized"); - } return getSession(user, password); } @@ -119,19 +117,11 @@ public abstract class SessionFactory { throw new UnsupportedOperationException("unauthenticated sessions are not supported"); } - public T init() { - LDAPServers ldapServers = ldapServers(config.settings()); - this.serverSet = serverSet(config.settings(), sslService, ldapServers); - this.sslUsed = ldapServers.ssl; - return (T) this; - } - protected static LDAPConnectionOptions connectionOptions(Settings settings) { LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setConnectTimeoutMillis(Math.toIntExact(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis())); options.setFollowReferrals(settings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true)); options.setResponseTimeoutMillis(settings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()); - options.setAutoReconnect(true); options.setAllowConcurrentSocketFactoryUse(true); if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); @@ -139,15 +129,19 @@ public abstract class SessionFactory { return options; } - protected LDAPServers ldapServers(Settings settings) { + protected final LDAPServers ldapServers(Settings settings) { // Parse LDAP urls - String[] ldapUrls = settings.getAsArray(URLS_SETTING); + String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings)); if (ldapUrls == null || ldapUrls.length == 0) { throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]"); } return new LDAPServers(ldapUrls); } + protected String[] getDefaultLdapUrls(Settings settings) { + return null; + } + protected ServerSet serverSet(Settings settings, ClientSSLService clientSSLService, LDAPServers ldapServers) { SocketFactory socketFactory = null; if (ldapServers.ssl()) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java index b275c31b210..2a02e94af60 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java @@ -31,7 +31,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("scope", LdapSearchScope.SUB_TREE) .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -48,7 +49,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .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(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("Users"))); } @@ -58,7 +60,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .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(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin"))); } @@ -69,8 +72,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-32-545", //Default Users group "S-1-5-21-3510024162-210737641-214529065-513" //Default Domain Users group }; - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -80,8 +84,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "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 - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -95,9 +100,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "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 - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Bruce Banner, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE); + + final String dn = BRUCE_BANNER_DN; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java index 1c01653718a..e0c48302f72 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java @@ -110,7 +110,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateUserPrincipleName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateUserPrincipleName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -122,7 +122,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateSAMAccountName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateSAMAccountName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -144,7 +144,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachesSuccesfulAuthentications() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachesSuccesfulAuthentications", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -160,7 +160,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingCanBeDisabled() throws Exception { Settings settings = settings(Settings.builder().put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING, -1).build()); RealmConfig config = new RealmConfig("testAuthenticateCachingCanBeDisabled", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -176,7 +176,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingClearsCacheOnRoleMapperRefresh() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachingClearsCacheOnRoleMapperRefresh", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -203,7 +203,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -217,7 +217,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java index 633685400b7..4347c8e9bdb 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java @@ -29,7 +29,7 @@ public class ActiveDirectoryRealmUsageTests extends AbstractActiveDirectoryInteg .put("load_balance.type", loadBalanceType) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, mock(DnRoleMapper.class)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java index 78da9be72a1..5aa133a5482 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java @@ -5,7 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; -import org.elasticsearch.ElasticsearchSecurityException; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory; @@ -19,7 +19,7 @@ import org.elasticsearch.test.junit.annotations.Network; import java.io.IOException; import java.util.List; -import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; @@ -31,7 +31,7 @@ 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, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -49,29 +49,45 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") + public void testNetbiosAuth() throws Exception { + final String adUrl = randomFrom("ldap://54.213.145.20:3268", "ldaps://54.213.145.20:3269", AD_LDAP_URL); + RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); + + String userName = "ades\\ironman"; + try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { + List groups = ldap.groups(); + assertThat(groups, containsInAnyOrder( + containsString("Geniuses"), + containsString("Billionaire"), + containsString("Playboy"), + containsString("Philanthropists"), + containsString("Avengers"), + containsString("SHIELD"), + containsString("CN=Users,CN=Builtin"), + containsString("Domain Users"), + containsString("Supers"))); + } + } + public void testTcpReadTimeout() throws Exception { Settings settings = Settings.builder() .put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchSecurityException e) { - assertAuthenticationException(e); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testAdAuthAvengers() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; for(String user: users) { @@ -86,7 +102,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -108,7 +124,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.BASE, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -134,7 +150,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -149,7 +165,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -167,7 +183,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //login with sAMAccountName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -191,7 +207,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI "(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName try (LdapSession ldap = sessionFactory.session("erik.selvig", SecuredStringTests.build(PASSWORD))) { @@ -217,7 +233,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -243,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -259,14 +275,12 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI public void testAdAuthWithHostnameVerification() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; - try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any active directory servers")); - } + LDAPException expected = + expectThrows(LDAPException.class, () -> sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } public void testStandardLdapHostnameVerification() throws Exception { @@ -277,14 +291,11 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(LdapSessionFactory.HOSTNAME_VERIFICATION_SETTING, true) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java index bec22ac6e9a..d08e1758074 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java @@ -45,7 +45,6 @@ public abstract class GroupsResolverTestCase extends ESTestCase { LDAPURL ldapurl = new LDAPURL(ldapUrl()); LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setFollowReferrals(true); - options.setAutoReconnect(true); options.setAllowConcurrentSocketFactoryUse(true); options.setConnectTimeoutMillis(Math.toIntExact(SessionFactory.TIMEOUT_DEFAULT.millis())); options.setResponseTimeoutMillis(SessionFactory.TIMEOUT_DEFAULT.millis()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 56588941cfe..6e2f159c6ee 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -66,7 +66,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -82,7 +82,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -98,7 +98,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -116,7 +116,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); DnRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, roleMapper); @@ -143,7 +143,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -216,7 +216,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm-userdn", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); User user = ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", SecuredStringTests.build(PASSWORD))); @@ -244,7 +244,7 @@ public class LdapRealmTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap-realm", settings.build(), globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm realm = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java index f4550255b1a..f5cd1250fc3 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java @@ -6,8 +6,8 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPURL; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -22,6 +22,7 @@ import org.junit.Before; import java.io.IOException; import java.util.List; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -49,7 +50,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -57,15 +58,14 @@ public class LdapSessionFactoryTests extends LdapTestCase { try (LdapSession session = sessionFactory.session(user, userPass)) { fail("expected connection timeout error here"); } catch (Exception e) { - assertThat(e, instanceOf(ElasticsearchSecurityException.class)); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting ")); + assertThat(e, instanceOf(LDAPException.class)); + assertThat(e.getMessage(), containsString("A client-side timeout was encountered while waiting ")); } finally { ldapServer.setProcessingDelayMillis(0L); } } @Network - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch-shield/issues/767") public void testConnectTimeout() { // Local sockets connect too fast... String ldapUrl = "ldap://54.200.235.244:389"; @@ -78,19 +78,17 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); long start = System.currentTimeMillis(); - try (LdapSession session = sessionFactory.session(user, userPass)) { - fail("expected connection timeout error here"); - } catch (Exception e) { - long time = System.currentTimeMillis() - start; - assertThat(time, lessThan(10000L)); - assertThat(e, instanceOf(IOException.class)); - assertThat(e.getCause().getCause().getMessage(), containsString("within the configured timeout of")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, userPass)); + long time = System.currentTimeMillis() - start; + assertThat(time, lessThan(10000L)); + assertThat(expected, instanceOf(LDAPException.class)); + assertThat(expected.getCause().getMessage(), + anyOf(containsString("within the configured timeout of"), containsString("connect timed out"))); } public void testBindWithTemplates() throws Exception { @@ -103,7 +101,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -124,15 +122,14 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); - try (LdapSession ldapConnection = ldapFac.session(user, userPass)) { - fail("Expected ElasticsearchSecurityException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), is("failed LDAP authentication")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> ldapFac.session(user, userPass)); + assertThat(expected.getMessage(), containsString("Unable to bind as user")); + Throwable[] suppressed = expected.getSuppressed(); + assertThat(suppressed.length, is(2)); } public void testGroupLookupSubtree() throws Exception { @@ -141,7 +138,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -158,7 +155,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; try (LdapSession ldap = ldapFac.session(user, SecuredStringTests.build("pass"))) { @@ -173,7 +170,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java index 87fb66a8a5c..60dfe8bbbe3 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java @@ -5,12 +5,12 @@ */ package org.elasticsearch.xpack.security.authc.ldap; -import com.carrotsearch.randomizedtesting.ThreadFilter; -import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; +import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.sdk.BindRequest; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; +import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldap.sdk.SingleServerSet; import org.elasticsearch.ElasticsearchSecurityException; @@ -18,8 +18,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.env.Environment; -import org.elasticsearch.node.MockNode; -import org.elasticsearch.node.Node; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectorySessionFactoryTests; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -31,17 +29,12 @@ import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; import org.elasticsearch.xpack.security.support.NoOpLogger; import org.elasticsearch.test.junit.annotations.Network; -import org.elasticsearch.xpack.watcher.Watcher; -import org.elasticsearch.xpack.XPackPlugin; import org.junit.Before; -import java.io.IOException; import java.nio.file.Path; import java.text.MessageFormat; -import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -51,12 +44,6 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -// thread leak filter for UnboundID's background connect threads. The background connect threads do not always respect the -// timeout and linger. Will be fixed in a new version of the library, see -// http://sourceforge.net/p/ldap-sdk/discussion/1001257/thread/154e3b71/ -@ThreadLeakFilters(filters = { - LdapUserSearchSessionFactoryTests.BackgroundConnectThreadLeakFilter.class -}) public class LdapUserSearchSessionFactoryTests extends LdapTestCase { private ClientSSLService clientSSLService; @@ -87,9 +74,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); try { assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true)); } finally { @@ -107,9 +95,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -142,9 +131,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.BASE) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -181,9 +171,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.BASE) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -216,9 +207,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -255,9 +247,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -289,9 +282,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "uid1") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -326,9 +320,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.base_dn", userSearchBase) .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "wbush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -361,9 +356,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "ironman@ad.test.elasticsearch.com") .put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(); RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try { @@ -403,8 +399,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.base_dn", userSearchBase) .put("bind_dn", "uid=blackwidow,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") .put("bind_password", OpenLdapTests.PASSWORD) + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService); String[] users = new String[] { "cap", "hawkeye", "hulk", "ironman", "thor" }; try { @@ -479,7 +476,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { } } - public void testThatEmptyBindDNThrowsExceptionWithHealthCheckEnabled() throws Exception { + public void testThatEmptyBindDNWithHealthCheckEnabledDoesNotThrow() throws Exception { String groupSearchBase = "o=sevenSeas"; String userSearchBase = "o=sevenSeas"; RealmConfig config = new RealmConfig("ldap_realm", Settings.builder() @@ -488,12 +485,13 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .build(), globalSettings); + LdapUserSearchSessionFactory searchSessionFactory = null; try { - new LdapUserSearchSessionFactory(config, null).init(); - fail("expected an exception"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("[bind_dn] has not been specified so a value must be specified for [user_search" + - ".pool.health_check.dn] or [user_search.pool.health_check.enabled] must be set to false")); + searchSessionFactory = new LdapUserSearchSessionFactory(config, null); + } finally { + if (searchSessionFactory != null) { + searchSessionFactory.shutdown(); + } } } @@ -512,12 +510,17 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { assertThat(simpleBindRequest.getBindDN(), is("cn=ironman")); } - @Network - public void testThatLDAPServerConnectErrorDoesNotPreventNodeFromStarting() throws IOException { + public void testThatConnectErrorIsNotThrownOnConstruction() throws Exception { String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; + + // pick a random ldap server and stop it + InMemoryDirectoryServer inMemoryDirectoryServer = randomFrom(ldapServers); + String ldapUrl = new LDAPURL("ldap", "localhost", inMemoryDirectoryServer.getListenPort(), null, null, null, null).toString(); + inMemoryDirectoryServer.shutDown(true); + Settings ldapSettings = Settings.builder() - .put(LdapTestCase.buildLdapSettings(new String[] { "ldaps://elastic.co:636" }, Strings.EMPTY_ARRAY, + .put(LdapTestCase.buildLdapSettings(new String[] { ldapUrl }, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put("user_search.base_dn", userSearchBase) .put("bind_dn", "ironman@ad.test.elasticsearch.com") @@ -525,30 +528,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.attribute", "cn") .put("timeout.tcp_connect", "500ms") .put("type", "ldap") + .put("user_search.pool.health_check.enabled", false) + .put("user_search.pool.enabled", randomBoolean()) .build(); - Settings.Builder builder = Settings.builder(); - for (Map.Entry entry : ldapSettings.getAsMap().entrySet()) { - builder.put("xpack.security.authc.realms.ldap1." + entry.getKey(), entry.getValue()); - } - builder.put("path.home", createTempDir()); - - // disable watcher, because watcher takes some time when starting, which results in problems - // having a quick start/stop cycle like below - builder.put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false); - - try (Node node = new MockNode(builder.build(), Collections.singletonList(XPackPlugin.class))) { - node.start(); - } - } - - public static class BackgroundConnectThreadLeakFilter implements ThreadFilter { - @Override - public boolean reject(Thread thread) { - if (thread.getName().startsWith("Background connect thread for elastic.co")) { - return true; + RealmConfig config = new RealmConfig("ldap_realm", ldapSettings, globalSettings); + LdapUserSearchSessionFactory searchSessionFactory = null; + try { + searchSessionFactory = new LdapUserSearchSessionFactory(config, null); + } finally { + if (searchSessionFactory != null) { + searchSessionFactory.shutdown(); } - return false; } } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java index 95da2786d0d..d3c7facba65 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -25,6 +26,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Map; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasItem; @@ -67,7 +69,7 @@ public class OpenLdapTests extends ESTestCase { String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; RealmConfig config = new RealmConfig("oldap-test", buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; for (String user : users) { @@ -84,7 +86,7 @@ public class OpenLdapTests extends ESTestCase { String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; RealmConfig config = new RealmConfig("oldap-test", buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; for (String user : users) { @@ -111,7 +113,7 @@ public class OpenLdapTests extends ESTestCase { settings.put("load_balance.type", loadBalanceType); RealmConfig config = new RealmConfig("oldap-test", settings.build(), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapRealm realm = new LdapRealm(config, sessionFactory, mock(DnRoleMapper.class)); Map stats = realm.usageStats(); @@ -133,32 +135,28 @@ public class OpenLdapTests extends ESTestCase { .put("group_search.user_attribute", "uid") .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); try (LdapSession ldap = sessionFactory.session("selvig", SecuredStringTests.build(PASSWORD))){ assertThat(ldap.groups(), hasItem(containsString("Geniuses"))); } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") public void testTcpTimeout() throws Exception { 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"; Settings settings = Settings.builder() - .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL)) + .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("thor", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchException e) { - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("thor", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testStandardLdapConnectionHostnameVerification() throws Exception { @@ -171,14 +169,11 @@ public class OpenLdapTests extends ESTestCase { .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "blackwidow"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, LdapSearchScope scope) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java index df0ec6d60a0..7a0c638a5c8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.authc.ldap; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -31,7 +30,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -46,7 +45,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -61,7 +60,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Avengers"))); } @@ -74,7 +73,19 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { SearchGroupsResolver resolver = new SearchGroupsResolver(settings); List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", - TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); + assertThat(groups, hasItem(containsString("Geniuses"))); + } + + public void testFilterIncludesPosixGroups() throws Exception { + Settings settings = Settings.builder() + .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("user_attribute", "uid") + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Geniuses"))); } @@ -116,12 +127,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .put("user_attribute", "doesntExists") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - try { - resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE); - fail("searching for a non-existing attribute should throw an LdapException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), containsString("no results returned")); - } + assertNull(resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE)); } public void testReadBinaryUserAttribute() throws Exception { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java index f297c920e7e..97a43ad228e 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java @@ -26,7 +26,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { public void testResolve() throws Exception { //falling back on the 'memberOf' attribute UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(Settings.EMPTY); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -39,7 +39,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "seeAlso") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, hasItems(containsString("Avengers"))); //seeAlso only has Avengers } @@ -48,7 +48,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "doesntExist") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, empty()); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java index 2201bff81f7..3e9f855e689 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java @@ -166,7 +166,7 @@ public class SessionFactoryLoadBalancingTests extends LdapTestCase { LdapSearchScope.SUB_TREE, loadBalancing); RealmConfig config = new RealmConfig("test-session-factory", settings, Settings.builder().put("path.home", createTempDir()).build()); - return new TestSessionFactory(config, null).init(); + return new TestSessionFactory(config, null); } static class TestSessionFactory extends SessionFactory { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java index 5d6a87f88dc..cf78c0d5302 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java @@ -63,6 +63,6 @@ public class SessionFactoryTests extends ESTestCase { protected LdapSession getSession(String user, SecuredString password) { return null; } - }.init(); + }; } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java index 2be39ee278f..7f40fff068d 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java @@ -125,15 +125,17 @@ public class HandshakeWaitingHandlerTests extends ESTestCase { } if (failureCause.get() != null) { + logger.info("run [{}] produced a failure", i); assertThat(failureCause.get(), anyOf(instanceOf(SSLException.class), instanceOf(AssertionError.class))); break; + } else { + logger.warn("run [{}] did not produce a failure", i); } } assertThat("Expected this test to fail with an SSLException or AssertionError", failureCause.get(), notNullValue()); } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/533") public void testWriteBeforeHandshakePassesWithHandshakeWaitingHandler() throws Exception { clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() { @@ -161,14 +163,15 @@ public class HandshakeWaitingHandlerTests extends ESTestCase { // Wait for pending writes to prevent IOExceptions Channel channel = handshakeFuture.getChannel(); HandshakeWaitingHandler handler = channel.getPipeline().get(HandshakeWaitingHandler.class); - while (handler != null && handler.hasPendingWrites()) { - Thread.sleep(10); + if (handler != null) { + boolean noMoreWrites = awaitBusy(() -> handler.hasPendingWrites() == false); + assertTrue(noMoreWrites); } - channel.close(); } assertNotFailed(); + logger.info("run [{}] succeeded", i); } }