add the ability to utilize load balancing and failover for ldap

Previously we only exposed the use of a single URL for LDAP realms, while the code supported
multiple URLs. Internally we always used a failover server set, which would have provided failover
to another LDAP server if multiple existed. This change introduces a new setting `load_balance.type`
on the realm that indicates the type of load balancing. Valid options are:

* `failover` - the first server in the list will be used until it fails and then additional servers will be tried until
one succeeds. The first successful server will be used from now on. This is the default.
* `round_robin` - continuously iterates through the list of servers for each new connection. If a server is down,
the iteration will continue until a successful connection is made. The downfall here is that the list does not
get reordered on a down server, so there is overhead for always trying the servers in order.
* `dns_failover` - This server set takes a single URL that uses a DNS that will resolve to multiple IP addresses.
Connections will be consistently attempted to servers in the order they are retrieved from the name service; there
is no re-ordering and the first successful connection will be used.
* `dns_round_robin` - This server set takes a single URL that uses a DNS that will resolve to multiple IP addresses.
The addresses retrieved from the name service will connected to in the same order as `round_robin`.

Closes elastic/elasticsearch#31

Original commit: elastic/x-pack-elasticsearch@9ce9a1bf23
This commit is contained in:
jaymode 2015-12-21 15:02:38 -05:00
parent f68f9aa268
commit c5592ee3be
14 changed files with 516 additions and 155 deletions

View File

@ -5,13 +5,10 @@
*/
package org.elasticsearch.shield.authc.activedirectory;
import com.unboundid.ldap.sdk.FailoverServerSet;
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 com.unboundid.ldap.sdk.ServerSet;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsFilter;
@ -23,7 +20,6 @@ import org.elasticsearch.shield.authc.ldap.support.SessionFactory;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.ClientSSLService;
import javax.net.SocketFactory;
import java.io.IOException;
import static org.elasticsearch.shield.authc.ldap.support.LdapUtils.createFilter;
@ -51,10 +47,9 @@ public class ActiveDirectorySessionFactory extends SessionFactory {
private final String userSearchFilter;
private final LdapSearchScope userSearchScope;
private final GroupsResolver groupResolver;
private final ServerSet ldapServerSet;
public ActiveDirectorySessionFactory(RealmConfig config, ClientSSLService sslService) {
super(config);
super(config, sslService);
Settings settings = config.settings();
domainName = settings.get(AD_DOMAIN_NAME_SETTING);
if (domainName == null) {
@ -64,7 +59,6 @@ public class ActiveDirectorySessionFactory extends SessionFactory {
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 + ")))");
ldapServerSet = serverSet(config.settings(), sslService);
groupResolver = new ActiveDirectoryGroupsResolver(settings.getAsSettings("group_search"), domainDN);
}
@ -72,24 +66,10 @@ public class ActiveDirectorySessionFactory extends SessionFactory {
filter.filterOut("shield.authc.realms." + realmName + "." + HOSTNAME_VERIFICATION_SETTING);
}
ServerSet serverSet(Settings settings, ClientSSLService clientSSLService) {
@Override
protected LDAPServers ldapServers(Settings settings) {
String[] ldapUrls = settings.getAsArray(URLS_SETTING, new String[] { "ldap://" + domainName + ":389" });
LDAPServers servers = new LDAPServers(ldapUrls);
LDAPConnectionOptions options = connectionOptions(settings);
SocketFactory socketFactory;
if (servers.ssl()) {
socketFactory = clientSSLService.sslSocketFactory();
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
logger.debug("using encryption for LDAP connections without hostname verification");
}
} else {
socketFactory = null;
}
FailoverServerSet serverSet = new FailoverServerSet(servers.addresses(), servers.ports(), socketFactory, options);
serverSet.setReOrderOnFailover(true);
return serverSet;
return new LDAPServers(ldapUrls);
}
/**
@ -103,7 +83,7 @@ public class ActiveDirectorySessionFactory extends SessionFactory {
LDAPConnection connection;
try {
connection = ldapServerSet.getConnection();
connection = serverSet.getConnection();
} catch (LDAPException e) {
throw new IOException("failed to connect to any active directory servers", e);
}

View File

@ -5,11 +5,8 @@
*/
package org.elasticsearch.shield.authc.ldap;
import com.unboundid.ldap.sdk.FailoverServerSet;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ServerSet;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.LdapSession;
@ -19,7 +16,6 @@ import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.shield.support.Exceptions;
import javax.net.SocketFactory;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;
@ -38,43 +34,17 @@ public class LdapSessionFactory extends SessionFactory {
private final String[] userDnTemplates;
private final GroupsResolver groupResolver;
private final ServerSet ldapServerSet;
public LdapSessionFactory(RealmConfig config, ClientSSLService sslService) {
super(config);
super(config, sslService);
Settings settings = config.settings();
userDnTemplates = settings.getAsArray(USER_DN_TEMPLATES_SETTING);
if (userDnTemplates == null) {
throw new IllegalArgumentException("missing required LDAP setting [" + USER_DN_TEMPLATES_SETTING + "]");
}
this.ldapServerSet = serverSet(config.settings(), sslService);
groupResolver = groupResolver(settings);
}
ServerSet serverSet(Settings settings, ClientSSLService clientSSLService) {
// Parse LDAP urls
String[] ldapUrls = settings.getAsArray(URLS_SETTING);
if (ldapUrls == null || ldapUrls.length == 0) {
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]");
}
LDAPServers servers = new LDAPServers(ldapUrls);
LDAPConnectionOptions options = connectionOptions(settings);
SocketFactory socketFactory;
if (servers.ssl()) {
socketFactory = clientSSLService.sslSocketFactory();
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
logger.debug("using encryption for LDAP connections without hostname verification");
}
} else {
socketFactory = null;
}
FailoverServerSet serverSet = new FailoverServerSet(servers.addresses(), servers.ports(), socketFactory, options);
serverSet.setReOrderOnFailover(true);
return serverSet;
}
/**
* This iterates through the configured user templates attempting to open. If all attempts fail, the last exception
* is kept as the cause of the thrown exception
@ -87,7 +57,7 @@ public class LdapSessionFactory extends SessionFactory {
LDAPConnection connection;
try {
connection = ldapServerSet.getConnection();
connection = serverSet.getConnection();
} catch (LDAPException e) {
throw new IOException("failed to connect to any LDAP servers", e);
}

View File

@ -5,10 +5,8 @@
*/
package org.elasticsearch.shield.authc.ldap;
import com.unboundid.ldap.sdk.FailoverServerSet;
import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.SearchRequest;
@ -29,7 +27,6 @@ import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.shield.support.Exceptions;
import javax.net.SocketFactory;
import java.io.IOException;
import java.util.Locale;
@ -48,12 +45,11 @@ public class LdapUserSearchSessionFactory extends SessionFactory {
private final String userSearchBaseDn;
private final LdapSearchScope scope;
private final String userAttribute;
private final ServerSet serverSet;
private LDAPConnectionPool connectionPool;
public LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) {
super(config);
super(config, sslService);
Settings settings = config.settings();
userSearchBaseDn = settings.get("user_search.base_dn");
if (userSearchBaseDn == null) {
@ -61,7 +57,6 @@ 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);
serverSet = serverSet(settings, sslService);
connectionPool = createConnectionPool(config, serverSet, timeout, logger);
groupResolver = groupResolver(settings);
}
@ -126,30 +121,6 @@ public class LdapUserSearchSessionFactory extends SessionFactory {
return request;
}
ServerSet serverSet(Settings settings, ClientSSLService clientSSLService) {
// Parse LDAP urls
String[] ldapUrls = settings.getAsArray(URLS_SETTING);
if (ldapUrls == null || ldapUrls.length == 0) {
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]");
}
LDAPServers servers = new LDAPServers(ldapUrls);
LDAPConnectionOptions options = connectionOptions(settings);
SocketFactory socketFactory;
if (servers.ssl()) {
socketFactory = clientSSLService.sslSocketFactory();
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
logger.debug("using encryption for LDAP connections without hostname verification");
}
} else {
socketFactory = null;
}
FailoverServerSet serverSet = new FailoverServerSet(servers.addresses(), servers.ports(), socketFactory, options);
serverSet.setReOrderOnFailover(true);
return serverSet;
}
@Override
public LdapSession session(String user, SecuredString password) throws Exception {
try {

View File

@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap.support;
import com.unboundid.ldap.sdk.FailoverServerSet;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.RoundRobinDNSServerSet;
import com.unboundid.ldap.sdk.RoundRobinServerSet;
import com.unboundid.ldap.sdk.ServerSet;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import javax.net.SocketFactory;
import java.util.Arrays;
import java.util.Locale;
/**
* Enumeration representing the various supported {@link ServerSet} types that can be used with out built in realms.
*/
public enum LdapLoadBalancing {
FAILOVER() {
@Override
ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options) {
FailoverServerSet serverSet = new FailoverServerSet(addresses, ports, socketFactory, options);
serverSet.setReOrderOnFailover(true);
return serverSet;
}
},
ROUND_ROBIN() {
@Override
ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options) {
return new RoundRobinServerSet(addresses, ports, socketFactory, options);
}
},
DNS_ROUND_ROBIN() {
@Override
ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options) {
if (addresses.length != 1) {
throw new IllegalArgumentException(toString() + " can only be used with a single url");
}
if (InetAddresses.isInetAddress(addresses[0])) {
throw new IllegalArgumentException(toString() + " can only be used with a DNS name");
}
TimeValue dnsTtl = settings.getAsTime("cache_ttl", TimeValue.timeValueHours(1L));
return new RoundRobinDNSServerSet(addresses[0], ports[0],
RoundRobinDNSServerSet.AddressSelectionMode.ROUND_ROBIN, dnsTtl.millis(), null, socketFactory, options);
}
},
DNS_FAILOVER() {
@Override
ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options) {
if (addresses.length != 1) {
throw new IllegalArgumentException(toString() + " can only be used with a single url");
}
if (InetAddresses.isInetAddress(addresses[0])) {
throw new IllegalArgumentException(toString() + " can only be used with a DNS name");
}
TimeValue dnsTtl = settings.getAsTime("cache_ttl", TimeValue.timeValueHours(1L));
return new RoundRobinDNSServerSet(addresses[0], ports[0],
RoundRobinDNSServerSet.AddressSelectionMode.FAILOVER, dnsTtl.millis(), null, socketFactory, options);
}
};
public static final String LOAD_BALANCE_SETTINGS = "load_balance";
public static final String LOAD_BALANCE_TYPE_SETTING = "type";
public static final String LOAD_BALANCE_TYPE_DEFAULT = LdapLoadBalancing.FAILOVER.toString();
abstract ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options);
@Override
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}
public static ServerSet serverSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options) {
Settings loadBalanceSettings = settings.getAsSettings(LOAD_BALANCE_SETTINGS);
String type = loadBalanceSettings.get(LOAD_BALANCE_TYPE_SETTING, LOAD_BALANCE_TYPE_DEFAULT);
switch (type.toLowerCase(Locale.ENGLISH)) {
case "failover":
return FAILOVER.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "dns_failover":
return DNS_FAILOVER.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "round_robin":
return ROUND_ROBIN.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "dns_round_robin":
return DNS_ROUND_ROBIN.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
default:
throw new IllegalArgumentException("unknown server set type [" + type + "]. value must be one of " + Arrays.toString(LdapLoadBalancing.values()));
}
}
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap.support;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPURL;
import com.unboundid.ldap.sdk.ServerSet;
import com.unboundid.util.ssl.HostNameSSLSocketVerifier;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
@ -15,7 +16,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.ClientSSLService;
import javax.net.SocketFactory;
import java.util.regex.Pattern;
import static java.util.Arrays.asList;
@ -48,8 +51,9 @@ public abstract class SessionFactory {
protected final ESLogger connectionLogger;
protected final RealmConfig config;
protected final TimeValue timeout;
protected final ServerSet serverSet;
protected SessionFactory(RealmConfig config) {
protected SessionFactory(RealmConfig config, ClientSSLService sslService) {
this.config = config;
this.logger = config.logger(getClass());
this.connectionLogger = config.logger(getClass());
@ -59,6 +63,7 @@ public abstract class SessionFactory {
searchTimeout = TimeValue.timeValueSeconds(1L);
}
this.timeout = searchTimeout;
this.serverSet = serverSet(config.settings(), sslService, ldapServers(config.settings()));
}
/**
@ -104,6 +109,33 @@ public abstract class SessionFactory {
return options;
}
protected LDAPServers ldapServers(Settings settings) {
// Parse LDAP urls
String[] ldapUrls = settings.getAsArray(URLS_SETTING);
if (ldapUrls == null || ldapUrls.length == 0) {
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]");
}
return new LDAPServers(ldapUrls);
}
protected ServerSet serverSet(Settings settings, ClientSSLService clientSSLService, LDAPServers ldapServers) {
SocketFactory socketFactory = null;
if (ldapServers.ssl()) {
socketFactory = clientSSLService.sslSocketFactory();
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
logger.debug("using encryption for LDAP connections without hostname verification");
}
}
return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings, socketFactory, connectionOptions(settings));
}
// package private to use for testing
ServerSet getServerSet() {
return serverSet;
}
public static class LDAPServers {
private final String[] addresses;

View File

@ -11,6 +11,8 @@ import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPURL;
import com.unboundid.ldap.sdk.schema.Schema;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.RealmConfig;
@ -24,6 +26,10 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.HOSTNAME_VERIFICATION_SETTING;
import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.URLS_SETTING;
@ -53,12 +59,18 @@ import static org.mockito.Mockito.verify;
public class ActiveDirectoryRealmTests extends ESTestCase {
private static final String PASSWORD = "password";
private InMemoryDirectoryServer directoryServer;
protected static int numberOfLdapServers;
protected InMemoryDirectoryServer[] directoryServers;
private ResourceWatcherService resourceWatcherService;
private ThreadPool threadPool;
private Settings globalSettings;
@BeforeClass
public static void setNumberOfLdapServers() {
numberOfLdapServers = randomIntBetween(1, 4);
}
@Before
public void start() throws Exception {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=ad,dc=test,dc=elasticsearch,dc=com");
@ -69,10 +81,14 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
config.addAdditionalBindCredentials("CN=ironman@ad.test.elasticsearch.com", PASSWORD);
config.addAdditionalBindCredentials("CN=Thor@ad.test.elasticsearch.com", PASSWORD);
directoryServer = new InMemoryDirectoryServer(config);
directoryServers = new InMemoryDirectoryServer[numberOfLdapServers];
for (int i = 0; i < numberOfLdapServers; i++) {
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
directoryServer.add("dc=ad,dc=test,dc=elasticsearch,dc=com", new Attribute("dc", "UnboundID"), new Attribute("objectClass", "top", "domain", "extensibleObject"));
directoryServer.importFromLDIF(false, getDataPath("ad.ldif").toString());
directoryServer.startListening();
directoryServers[i] = directoryServer;
}
threadPool = new ThreadPool("active directory realm tests");
resourceWatcherService = new ResourceWatcherService(Settings.EMPTY, threadPool);
globalSettings = Settings.builder().put("path.home", createTempDir()).build();
@ -82,7 +98,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
public void stop() throws InterruptedException {
resourceWatcherService.stop();
terminate(threadPool);
directoryServer.shutDown(true);
for (int i = 0; i < numberOfLdapServers; i++) {
directoryServers[i].shutDown(true);
}
}
public void testAuthenticateUserPrincipleName() throws Exception {
@ -110,9 +128,13 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
assertThat(user.roles(), arrayContaining(containsString("Avengers")));
}
private String ldapUrl() throws LDAPException {
LDAPURL url = new LDAPURL("ldap", "localhost", directoryServer.getListenPort(), null, null, null, null);
return url.toString();
protected String[] ldapUrls() throws LDAPException {
List<String> urls = new ArrayList<>(numberOfLdapServers);
for (int i = 0; i < numberOfLdapServers; i++) {
LDAPURL url = new LDAPURL("ldap", "localhost", directoryServers[i].getListenPort(), null, null, null, null);
urls.add(url.toString());
}
return urls.toArray(Strings.EMPTY_ARRAY);
}
public void testAuthenticateCachesSuccesfulAuthentications() throws Exception {
@ -206,7 +228,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
private Settings settings(Settings extraSettings) throws Exception {
return Settings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(URLS_SETTING, ldapUrls())
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, "ad.test.elasticsearch.com")
.put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
.put(HOSTNAME_VERIFICATION_SETTING, false)

View File

@ -248,7 +248,7 @@ public class ActiveDirectorySessionFactoryTests extends ESTestCase {
@SuppressWarnings("unchecked")
public void testStandardLdapWithAttributeGroups() throws Exception {
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, false);
Settings settings = LdapTestCase.buildLdapSettings(new String[] { AD_LDAP_URL }, userTemplate, false);
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings);
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);

View File

@ -59,7 +59,7 @@ public class LdapRealmTests extends LdapTestCase {
public void testAuthenticateSubTreeGroupSearch() throws Exception {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE);
Settings settings = buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE);
RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings);
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService));
@ -73,7 +73,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.build();
RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings);
@ -89,7 +89,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.build();
RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings);
@ -107,7 +107,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.build();
RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings);
@ -133,7 +133,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(LdapRealm.CACHE_TTL_SETTING, -1)
.build();
RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings);
@ -152,7 +152,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(URLS_SETTING, ldapUrls())
.putArray(USER_DN_TEMPLATES_SETTING, userTemplate)
.put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", LdapSearchScope.SUB_TREE)
@ -166,7 +166,7 @@ public class LdapRealmTests extends LdapTestCase {
public void testLdapRealmSelectsLdapUserSearchSessionFactory() throws Exception {
String groupSearchBase = "o=sevenSeas";
Settings settings = Settings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(URLS_SETTING, ldapUrls())
.put("user_search.base_dn", "")
.put("bind_dn", "cn=Thomas Masterman Hardy,ou=people,o=sevenSeas")
.put("bind_password", PASSWORD)
@ -185,7 +185,7 @@ public class LdapRealmTests extends LdapTestCase {
public void testLdapRealmThrowsExceptionForUserTemplateAndSearchSettings() throws Exception {
Settings settings = Settings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(URLS_SETTING, ldapUrls())
.putArray(USER_DN_TEMPLATES_SETTING, "cn=foo")
.put("user_search.base_dn", "cn=bar")
.put("group_search.base_dn", "")
@ -205,7 +205,7 @@ public class LdapRealmTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("/org/elasticsearch/shield/authc/support/role_mapping.yml"))
.build();
RealmConfig config = new RealmConfig("test-ldap-realm-userdn", settings, globalSettings);

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.shield.authc.ldap;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.sdk.LDAPURL;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
@ -35,11 +37,11 @@ public class LdapSessionFactoryTests extends LdapTestCase {
}
public void testBindWithReadTimeout() throws Exception {
String ldapUrl = ldapUrl();
InMemoryDirectoryServer ldapServer = randomFrom(ldapServers);
String ldapUrl = new LDAPURL("ldap", "localhost", ldapServer.getListenPort(), null, null, null, null).toString();
String groupSearchBase = "o=sevenSeas";
String[] userTemplates = new String[] {
"cn={0},ou=people,o=sevenSeas",
};
String userTemplates = "cn={0},ou=people,o=sevenSeas";
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond
@ -68,9 +70,8 @@ public class LdapSessionFactoryTests extends LdapTestCase {
// Local sockets connect too fast...
String ldapUrl = "ldap://54.200.235.244:389";
String groupSearchBase = "o=sevenSeas";
String[] userTemplates = new String[] {
"cn={0},ou=people,o=sevenSeas",
};
String userTemplates = "cn={0},ou=people,o=sevenSeas";
Settings settings = Settings.builder()
.put(buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(SessionFactory.TIMEOUT_TCP_CONNECTION_SETTING, "1ms") //1 millisecond
@ -86,7 +87,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
fail("expected connection timeout error here");
} catch (Throwable t) {
long time = System.currentTimeMillis() - start;
assertThat(time, lessThan(10000l));
assertThat(time, lessThan(10000L));
assertThat(t, instanceOf(IOException.class));
assertThat(t.getCause().getCause().getMessage(), containsString("within the configured timeout of"));
}
@ -99,7 +100,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
"wrongname={0},ou=people,o=sevenSeas",
"cn={0},ou=people,o=sevenSeas", //this last one should work
};
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrl(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null);
@ -119,7 +120,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
"wrongname={0},ou=people,o=sevenSeas",
"asdf={0},ou=people,o=sevenSeas", //none of these should work
};
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrl(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
LdapSessionFactory ldapFac = new LdapSessionFactory(config, null);
@ -135,7 +136,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
public void testGroupLookupSubtree() throws Exception {
String groupSearchBase = "o=sevenSeas";
String userTemplate = "cn={0},ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings);
LdapSessionFactory ldapFac = new LdapSessionFactory(config, null);
@ -151,7 +152,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
public void testGroupLookupOneLevel() throws Exception {
String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
String userTemplate = "cn={0},ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings);
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings);
LdapSessionFactory ldapFac = new LdapSessionFactory(config, null);
@ -165,7 +166,7 @@ public class LdapSessionFactoryTests extends LdapTestCase {
public void testGroupLookupBase() throws Exception {
String groupSearchBase = "cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas";
String userTemplate = "cn={0},ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrl(), userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings);
RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings);
LdapSessionFactory ldapFac = new LdapSessionFactory(config, null);

View File

@ -36,7 +36,6 @@ import org.junit.Before;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@ -80,7 +79,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
public void testSupportsUnauthenticatedSessions() throws Exception {
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, "", LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, "", LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", "")
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -100,7 +99,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -134,7 +133,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -171,7 +170,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "cn=William Bush,ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -206,7 +205,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -243,7 +242,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -278,7 +277,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -314,7 +313,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -347,7 +346,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = settingsBuilder()
.put(LdapTestCase.buildLdapSettings(ActiveDirectorySessionFactoryTests.AD_LDAP_URL, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(LdapTestCase.buildLdapSettings(new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL }, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "ironman@ad.test.elasticsearch.com")
.put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
@ -389,7 +388,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
RealmConfig config = new RealmConfig("oldap-test", settingsBuilder()
.put(LdapTestCase.buildLdapSettings(OpenLdapTests.OPEN_LDAP_URL, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.put(LdapTestCase.buildLdapSettings(new String[] { OpenLdapTests.OPEN_LDAP_URL }, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.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)
@ -420,13 +419,13 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.build(), globalSettings);
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost", ldapServer.getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost", randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
try {
assertThat(connectionPool.getCurrentAvailableConnections(), is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_INITIAL_SIZE));
assertThat(connectionPool.getMaximumAvailableConnections(), is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_SIZE));
@ -443,7 +442,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
@ -452,7 +451,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put("user_search.pool.health_check.enabled", false)
.build(), globalSettings);
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost", ldapServer.getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost", randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
try {
assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
assertThat(connectionPool.getMaximumAvailableConnections(), is(12));
@ -467,7 +466,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_password", "pass")
.build(), globalSettings);
@ -499,7 +498,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings ldapSettings = settingsBuilder()
.put(LdapTestCase.buildLdapSettings("ldaps://elastic.co:636", Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put(LdapTestCase.buildLdapSettings(new String[] { "ldaps://elastic.co:636" }, Strings.EMPTY_ARRAY,
groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("bind_dn", "ironman@ad.test.elasticsearch.com")
.put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)

View File

@ -0,0 +1,104 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap.support;
import com.unboundid.ldap.sdk.FailoverServerSet;
import com.unboundid.ldap.sdk.RoundRobinDNSServerSet;
import com.unboundid.ldap.sdk.RoundRobinServerSet;
import com.unboundid.ldap.sdk.ServerSet;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
public class LdapLoadBalancingTests extends ESTestCase {
public void testBadTypeThrowsException() {
String badType = randomAsciiOfLengthBetween(3, 12);
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, badType).build();
try {
LdapLoadBalancing.serverSet(null, null, settings, null, null);
fail("using type [" + badType + "] should have thrown an exception");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("unknown server set type"));
}
}
public void testFailoverServerSet() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "failover").build();
String[] address = new String[] { "localhost" };
int[] ports = new int[] { 26000 };
ServerSet serverSet = LdapLoadBalancing.serverSet(address, ports, settings, null, null);
assertThat(serverSet, instanceOf(FailoverServerSet.class));
assertThat(((FailoverServerSet)serverSet).reOrderOnFailover(), is(true));
}
public void testDnsFailover() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "dns_failover").build();
String[] address = new String[] { "foo.bar" };
int[] ports = new int[] { 26000 };
ServerSet serverSet = LdapLoadBalancing.serverSet(address, ports, settings, null, null);
assertThat(serverSet, instanceOf(RoundRobinDNSServerSet.class));
assertThat(((RoundRobinDNSServerSet)serverSet).getAddressSelectionMode(), is(RoundRobinDNSServerSet.AddressSelectionMode.FAILOVER));
}
public void testDnsFailoverBadArgs() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "dns_failover").build();
String[] addresses = new String[] { "foo.bar", "localhost" };
int[] ports = new int[] { 26000, 389 };
try {
LdapLoadBalancing.serverSet(addresses, ports, settings, null, null);
fail("dns server sets only support a single URL");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("single url"));
}
try {
LdapLoadBalancing.serverSet(new String[] { "127.0.0.1" }, new int[] { 389 }, settings, null, null);
fail("dns server sets only support DNS names");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("DNS name"));
}
}
public void testRoundRobin() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "round_robin").build();
String[] address = new String[] { "localhost", "foo.bar" };
int[] ports = new int[] { 389, 389 };
ServerSet serverSet = LdapLoadBalancing.serverSet(address, ports, settings, null, null);
assertThat(serverSet, instanceOf(RoundRobinServerSet.class));
}
public void testDnsRoundRobin() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "dns_round_robin").build();
String[] address = new String[] { "foo.bar" };
int[] ports = new int[] { 26000 };
ServerSet serverSet = LdapLoadBalancing.serverSet(address, ports, settings, null, null);
assertThat(serverSet, instanceOf(RoundRobinDNSServerSet.class));
assertThat(((RoundRobinDNSServerSet)serverSet).getAddressSelectionMode(), is(RoundRobinDNSServerSet.AddressSelectionMode.ROUND_ROBIN));
}
public void testDnsRoundRobinBadArgs() {
Settings settings = Settings.builder().put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, "dns_round_robin").build();
String[] addresses = new String[] { "foo.bar", "localhost" };
int[] ports = new int[] { 26000, 389 };
try {
LdapLoadBalancing.serverSet(addresses, ports, settings, null, null);
fail("dns server sets only support a single URL");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("single url"));
}
try {
LdapLoadBalancing.serverSet(new String[] { "127.0.0.1" }, new int[] { 389 }, settings, null, null);
fail("dns server sets only support DNS names");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("DNS name"));
}
}
}

View File

@ -9,6 +9,7 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPURL;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.LdapRealm;
@ -17,6 +18,10 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.HOSTNAME_VERIFICATION_SETTING;
import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.URLS_SETTING;
@ -24,41 +29,68 @@ import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.USER_DN_TEM
public abstract class LdapTestCase extends ESTestCase {
protected InMemoryDirectoryServer ldapServer;
protected static int numberOfLdapServers;
protected InMemoryDirectoryServer[] ldapServers;
@BeforeClass
public static void setNumberOfLdapServers() {
numberOfLdapServers = randomIntBetween(1, 4);
}
@Before
public void startLdap() throws Exception {
ldapServer = new InMemoryDirectoryServer("o=sevenSeas");
ldapServers = new InMemoryDirectoryServer[numberOfLdapServers];
for (int i = 0; i < numberOfLdapServers; i++) {
InMemoryDirectoryServer ldapServer = new InMemoryDirectoryServer("o=sevenSeas");
ldapServer.add("o=sevenSeas", new Attribute("dc", "UnboundID"), new Attribute("objectClass", "top", "domain", "extensibleObject"));
ldapServer.importFromLDIF(false, getDataPath("/org/elasticsearch/shield/authc/ldap/support/seven-seas.ldif").toString());
ldapServer.startListening();
ldapServers[i] = ldapServer;
}
}
@After
public void stopLdap() throws Exception {
ldapServer.shutDown(true);
for (int i = 0; i < numberOfLdapServers; i++) {
ldapServers[i].shutDown(true);
}
}
protected String ldapUrl() throws LDAPException {
LDAPURL url = new LDAPURL("ldap", "localhost", ldapServer.getListenPort(), null, null, null, null);
return url.toString();
protected String[] ldapUrls() throws LDAPException {
List<String> urls = new ArrayList<>(numberOfLdapServers);
for (int i = 0; i < numberOfLdapServers; i++) {
LDAPURL url = new LDAPURL("ldap", "localhost", ldapServers[i].getListenPort(), null, null, null, null);
urls.add(url.toString());
}
return urls.toArray(Strings.EMPTY_ARRAY);
}
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, LdapSearchScope scope) {
return buildLdapSettings(new String[] { ldapUrl }, new String[] { userTemplate }, groupSearchBase, scope);
}
public static Settings buildLdapSettings(String[] ldapUrl, String userTemplate, String groupSearchBase, LdapSearchScope scope) {
return buildLdapSettings(ldapUrl, new String[] { userTemplate }, groupSearchBase, scope);
}
public static Settings buildLdapSettings(String ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope) {
return Settings.builder()
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope) {
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, null);
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope, LdapLoadBalancing serverSetType) {
Settings.Builder builder = Settings.builder()
.putArray(URLS_SETTING, ldapUrl)
.putArray(USER_DN_TEMPLATES_SETTING, userTemplate)
.put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", scope)
.put(HOSTNAME_VERIFICATION_SETTING, false)
.build();
.put(HOSTNAME_VERIFICATION_SETTING, false);
if (serverSetType != null) {
builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, serverSetType.toString());
}
return builder.build();
}
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, boolean hostnameVerification) {
public static Settings buildLdapSettings(String[] ldapUrl, String userTemplate, boolean hostnameVerification) {
return Settings.builder()
.putArray(URLS_SETTING, ldapUrl)
.putArray(USER_DN_TEMPLATES_SETTING, userTemplate)

View File

@ -0,0 +1,151 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap.support;
import com.unboundid.ldap.sdk.LDAPConnection;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.ssl.ClientSSLService;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
/**
* Tests that the server sets properly load balance connections without throwing exceptions
*/
public class SessionFactoryLoadBalancingTests extends LdapTestCase {
public void testRoundRobin() throws Exception {
TestSessionFactory testSessionFactory = createSessionFactory(LdapLoadBalancing.ROUND_ROBIN);
final int numberOfIterations = randomIntBetween(1, 5);
for (int iteration = 0; iteration < numberOfIterations; iteration++) {
for (int i = 0; i < numberOfLdapServers; i++) {
LDAPConnection connection = null;
try {
connection = testSessionFactory.getServerSet().getConnection();
assertThat(connection.getConnectedPort(), is(ldapServers[i].getListenPort()));
} finally {
if (connection != null) {
connection.close();
}
}
}
}
}
public void testRoundRobinWithFailures() throws Exception {
assumeTrue("at least one ldap server should be present for this test", ldapServers.length > 1);
TestSessionFactory testSessionFactory = createSessionFactory(LdapLoadBalancing.ROUND_ROBIN);
// create a list of ports
List<Integer> ports = new ArrayList<>(numberOfLdapServers);
for (int i = 0; i < ldapServers.length; i++) {
ports.add(ldapServers[i].getListenPort());
}
int numberToKill = randomIntBetween(1, numberOfLdapServers - 1);
for (int i = 0; i < numberToKill; i++) {
int index = randomIntBetween(0, numberOfLdapServers - 1);
ports.remove(Integer.valueOf(ldapServers[index].getListenPort()));
ldapServers[index].shutDown(true);
}
final int numberOfIterations = randomIntBetween(1, 5);
for (int iteration = 0; iteration < numberOfIterations; iteration++) {
for (Integer port : ports) {
LDAPConnection connection = null;
try {
connection = testSessionFactory.getServerSet().getConnection();
assertThat(connection.getConnectedPort(), is(port));
} finally {
if (connection != null) {
connection.close();
}
}
}
}
}
public void testFailover() throws Exception {
assumeTrue("at least one ldap server should be present for this test", ldapServers.length > 1);
TestSessionFactory testSessionFactory = createSessionFactory(LdapLoadBalancing.FAILOVER);
// first test that there is no round robin stuff going on
final int firstPort = ldapServers[0].getListenPort();
for (int i = 0; i < numberOfLdapServers; i++) {
LDAPConnection connection = null;
try {
connection = testSessionFactory.getServerSet().getConnection();
assertThat(connection.getConnectedPort(), is(firstPort));
} finally {
if (connection != null) {
connection.close();
}
}
}
List<Integer> stoppedServers = new ArrayList<>();
// now we should kill some servers including the first one
int numberToKill = randomIntBetween(1, numberOfLdapServers - 1);
// always kill the first one, but don't add to the list
ldapServers[0].shutDown(true);
stoppedServers.add(0);
for (int i = 0; i < numberToKill - 1; i++) {
int index = randomIntBetween(1, numberOfLdapServers - 1);
ldapServers[index].shutDown(true);
stoppedServers.add(index);
}
int firstNonStoppedPort = -1;
// now we find the first that isn't stopped
for (int i = 0; i < numberOfLdapServers; i++) {
if (stoppedServers.contains(i) == false) {
firstNonStoppedPort = ldapServers[i].getListenPort();
break;
}
}
assertThat(firstNonStoppedPort, not(-1));
final int numberOfIterations = randomIntBetween(1, 5);
for (int iteration = 0; iteration < numberOfIterations; iteration++) {
LDAPConnection connection = null;
try {
connection = testSessionFactory.getServerSet().getConnection();
assertThat(connection.getConnectedPort(), is(firstNonStoppedPort));
} finally {
if (connection != null) {
connection.close();
}
}
}
}
private TestSessionFactory createSessionFactory(LdapLoadBalancing loadBalancing) throws Exception {
String groupSearchBase = "cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas";
String userTemplate = "cn={0},ou=people,o=sevenSeas";
Settings settings = buildLdapSettings(ldapUrls(), new String[] { userTemplate }, groupSearchBase,
LdapSearchScope.SUB_TREE, loadBalancing);
RealmConfig config = new RealmConfig("test-session-factory", settings, Settings.builder().put("path.home", createTempDir()).build());
return new TestSessionFactory(config, null);
}
static class TestSessionFactory extends SessionFactory {
protected TestSessionFactory(RealmConfig config, ClientSSLService sslService) {
super(config, sslService);
}
@Override
public LdapSession session(String user, SecuredString password) throws Exception {
return null;
}
}
}

View File

@ -20,7 +20,6 @@ import static org.hamcrest.Matchers.is;
public class SessionFactoryTests extends ESTestCase {
public void testConnectionFactoryReturnsCorrectLDAPConnectionOptionsWithDefaultSettings() {
SessionFactory factory = createSessionFactory();
LDAPConnectionOptions options = SessionFactory.connectionOptions(Settings.EMPTY);
assertThat(options.followReferrals(), is(equalTo(true)));
assertThat(options.allowConcurrentSocketFactoryUse(), is(equalTo(true)));
@ -36,7 +35,6 @@ public class SessionFactoryTests extends ESTestCase {
.put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "20ms")
.put(SessionFactory.FOLLOW_REFERRALS_SETTING, "false")
.build();
SessionFactory factory = createSessionFactory();
LDAPConnectionOptions options = SessionFactory.connectionOptions(settings);
assertThat(options.followReferrals(), is(equalTo(false)));
assertThat(options.allowConcurrentSocketFactoryUse(), is(equalTo(true)));
@ -60,7 +58,7 @@ public class SessionFactoryTests extends ESTestCase {
private SessionFactory createSessionFactory() {
Settings global = settingsBuilder().put("path.home", createTempDir()).build();
return new SessionFactory(new RealmConfig("_name", Settings.EMPTY, global)) {
return new SessionFactory(new RealmConfig("_name", Settings.builder().put("url", "ldap://localhost:389").build(), global), null) {
@Override
public LdapSession session(String user, SecuredString password) {