[ldap] add user search with base dn and bind dn

This adds a second mode of operation to the ldap realm. This mode of operation
allows for single bind user to be specified. This bind user will be used to
search for user DNs starting from a base DN. The user DN will then be used to
authenticate via a bind operation. The bind user will then search for the user's
groups.

Closes elastic/elasticsearch#552
Closes elastic/elasticsearch#323

Original commit: elastic/x-pack-elasticsearch@3338730a64
This commit is contained in:
jaymode 2015-02-11 18:17:51 -08:00
parent d108faede3
commit c2a61d2207
17 changed files with 681 additions and 62 deletions

View File

@ -35,7 +35,7 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver {
this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE); this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE);
} }
public List<String> resolve(LDAPConnection connection, String userDn, TimeValue timeout, ESLogger logger) { public List<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
Filter groupSearchFilter = buildGroupQuery(connection, userDn, timeout, logger); Filter groupSearchFilter = buildGroupQuery(connection, userDn, timeout, logger);
logger.debug("group SID to DN search filter: [{}]", groupSearchFilter); logger.debug("group SID to DN search filter: [{}]", groupSearchFilter);
@ -59,7 +59,7 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver {
return groupList; return groupList;
} }
static Filter buildGroupQuery(LDAPConnection connection, String userDn, TimeValue timeout, ESLogger logger) { static Filter buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
try { try {
SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, "tokenGroups"); SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, "tokenGroups");
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds())); request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));

View File

@ -87,7 +87,7 @@ public class ActiveDirectorySessionFactory extends SessionFactory {
* @return An authenticated * @return An authenticated
*/ */
@Override @Override
public LdapSession open(String userName, SecuredString password) { public LdapSession session(String userName, SecuredString password) {
LDAPConnection connection; LDAPConnection connection;
try { try {

View File

@ -6,10 +6,13 @@
package org.elasticsearch.shield.authc.ldap; package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.AbstractLdapRealm; import org.elasticsearch.shield.authc.ldap.support.AbstractLdapRealm;
import org.elasticsearch.shield.authc.ldap.support.GroupToRoleMapper; import org.elasticsearch.shield.authc.ldap.support.GroupToRoleMapper;
import org.elasticsearch.shield.authc.ldap.support.SessionFactory;
import org.elasticsearch.shield.ssl.ClientSSLService; import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
@ -20,7 +23,7 @@ public class LdapRealm extends AbstractLdapRealm {
public static final String TYPE = "ldap"; public static final String TYPE = "ldap";
public LdapRealm(RealmConfig config, LdapSessionFactory ldap, GroupToRoleMapper roleMapper) { public LdapRealm(RealmConfig config, SessionFactory ldap, GroupToRoleMapper roleMapper) {
super(TYPE, config, ldap, roleMapper); super(TYPE, config, ldap, roleMapper);
} }
@ -38,9 +41,21 @@ public class LdapRealm extends AbstractLdapRealm {
@Override @Override
public LdapRealm create(RealmConfig config) { public LdapRealm create(RealmConfig config) {
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); SessionFactory sessionFactory = sessionFactory(config, clientSSLService);
GroupToRoleMapper roleMapper = new GroupToRoleMapper(TYPE, config, watcherService, null); GroupToRoleMapper roleMapper = new GroupToRoleMapper(TYPE, config, watcherService, null);
return new LdapRealm(config, sessionFactory, roleMapper); return new LdapRealm(config, sessionFactory, roleMapper);
} }
static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) {
Settings searchSettings = config.settings().getAsSettings("user_search");
if (!searchSettings.names().isEmpty()) {
if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) {
throw new ShieldSettingsException("settings were found for both user search and user template modes of operation. Please remove the settings for the\n"
+ "mode you do not wish to use. For more details refer to the ldap authentication section of the Shield guide.");
}
return new LdapUserSearchSessionFactory(config, clientSSLService);
}
return new LdapSessionFactory(config, clientSSLService);
}
} }
} }

View File

@ -77,7 +77,7 @@ public class LdapSessionFactory extends SessionFactory {
* @return authenticated exception * @return authenticated exception
*/ */
@Override @Override
public LdapSession open(String username, SecuredString password) { public LdapSession session(String username, SecuredString password) {
LDAPConnection connection; LDAPConnection connection;
try { try {
@ -115,7 +115,7 @@ public class LdapSessionFactory extends SessionFactory {
return MessageFormat.format(template, escapedUsername); return MessageFormat.format(template, escapedUsername);
} }
static LdapSession.GroupsResolver groupResolver(Settings settings) { static GroupsResolver groupResolver(Settings settings) {
Settings searchSettings = settings.getAsSettings("group_search"); Settings searchSettings = settings.getAsSettings("group_search");
if (!searchSettings.names().isEmpty()) { if (!searchSettings.names().isEmpty()) {
return new SearchGroupsResolver(searchSettings); return new SearchGroupsResolver(searchSettings);

View File

@ -0,0 +1,167 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap;
import com.unboundid.ldap.sdk.*;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.primitives.Ints;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.authc.ldap.support.LdapSession;
import org.elasticsearch.shield.authc.ldap.support.LdapSession.GroupsResolver;
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.util.Locale;
import static com.unboundid.ldap.sdk.Filter.createEqualityFilter;
import static com.unboundid.ldap.sdk.Filter.encodeValue;
import static org.elasticsearch.shield.authc.ldap.support.LdapUtils.searchForEntry;
public class LdapUserSearchSessionFactory extends SessionFactory {
static final int DEFAULT_CONNECTION_POOL_SIZE = 20;
static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 5;
static final String DEFAULT_USERNAME_ATTRIBUTE = "uid";
static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L);
private final GroupsResolver groupResolver;
private final LDAPConnectionPool connectionPool;
private final String userSearchBaseDn;
private final LdapSearchScope scope;
private final String userAttribute;
private final ServerSet serverSet;
public LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) {
super(config);
Settings settings = config.settings();
userSearchBaseDn = settings.get("user_search.base_dn");
if (userSearchBaseDn == null) {
throw new ShieldSettingsException("user_search base_dn must be specified");
}
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 = connectionPool(config.settings(), serverSet, timeout);
groupResolver = groupResolver(settings);
}
static LDAPConnectionPool connectionPool(Settings settings, ServerSet serverSet, TimeValue timeout) {
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);
try {
LDAPConnectionPool 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 ShieldSettingsException("[user_search.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");
}
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);
}
return pool;
} catch (LDAPException e) {
throw new ShieldLdapException("unable to connect to any LDAP servers", e);
}
}
static SimpleBindRequest bindRequest(Settings settings) {
SimpleBindRequest request = null;
String bindDn = settings.get("user_search.bind_dn");
if (bindDn != null) {
request = new SimpleBindRequest(bindDn, settings.get("user_search.bind_password"));
}
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 ShieldSettingsException("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) {
SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), Strings.EMPTY_ARRAY);
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));
try {
SearchResultEntry entry = searchForEntry(connectionPool, request, logger);
if (entry == null) {
throw new ShieldLdapException("failed to find user [" + user + "] with search base [" + userSearchBaseDn + "] scope [" + scope.toString().toLowerCase(Locale.ENGLISH) +"]");
}
String dn = entry.getDN();
tryBind(dn, password);
return new LdapSession(logger, connectionPool, dn, groupResolver, timeout);
} catch (LDAPException e) {
throw new ShieldLdapException("failed to authenticate user [" + user + "]", e);
}
}
private void tryBind(String dn, SecuredString password) {
LDAPConnection bindConnection;
try {
bindConnection = serverSet.getConnection();
} catch (LDAPException e) {
throw new ShieldLdapException("unable to connect to any LDAP servers for bind", e);
}
try {
bindConnection.bind(dn, new String(password.internalChars()));
} catch (LDAPException e) {
throw new ShieldLdapException("failed LDAP authentication", dn, e);
} finally {
bindConnection.close();
}
}
/*
* This method is used to cleanup the connections for tests
*/
void shutdown() {
connectionPool.close();
}
static GroupsResolver groupResolver(Settings settings) {
Settings searchSettings = settings.getAsSettings("group_search");
if (!searchSettings.names().isEmpty()) {
return new SearchGroupsResolver(searchSettings);
}
return new UserAttributeGroupsResolver(settings);
}
}

View File

@ -45,7 +45,7 @@ class SearchGroupsResolver implements GroupsResolver {
} }
@Override @Override
public List<String> resolve(LDAPConnection connection, String userDn, TimeValue timeout, ESLogger logger) { public List<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
List<String> groups = new LinkedList<>(); List<String> groups = new LinkedList<>();
String userId = userAttribute != null ? readUserAttribute(connection, userDn, timeout, logger) : userDn; String userId = userAttribute != null ? readUserAttribute(connection, userDn, timeout, logger) : userDn;
@ -63,7 +63,7 @@ class SearchGroupsResolver implements GroupsResolver {
return groups; return groups;
} }
String readUserAttribute(LDAPConnection connection, String userDn, TimeValue timeout, ESLogger logger) { String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
try { try {
SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute);
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds())); request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));

View File

@ -35,7 +35,7 @@ class UserAttributeGroupsResolver implements GroupsResolver {
} }
@Override @Override
public List<String> resolve(LDAPConnection connection, String userDn, TimeValue timeout, ESLogger logger) { public List<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
try { try {
SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute);
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds())); request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));

View File

@ -39,7 +39,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm {
*/ */
@Override @Override
protected User doAuthenticate(UsernamePasswordToken token) { protected User doAuthenticate(UsernamePasswordToken token) {
try (LdapSession session = sessionFactory.open(token.principal(), token.credentials())) { try (LdapSession session = sessionFactory.session(token.principal(), token.credentials())) {
List<String> groupDNs = session.groups(); List<String> groupDNs = session.groups();
Set<String> roles = roleMapper.mapRoles(groupDNs); Set<String> roles = roleMapper.mapRoles(groupDNs);
return new User.Simple(token.principal(), roles.toArray(new String[roles.size()])); return new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc.ldap.support; package org.elasticsearch.shield.authc.ldap.support;
import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPInterface;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
@ -18,7 +19,7 @@ import java.util.List;
public class LdapSession implements Closeable { public class LdapSession implements Closeable {
protected final ESLogger logger; protected final ESLogger logger;
protected final LDAPConnection ldapConnection; protected final LDAPInterface ldap;
protected final String bindDn; protected final String bindDn;
protected final GroupsResolver groupsResolver; protected final GroupsResolver groupsResolver;
protected final TimeValue timeout; protected final TimeValue timeout;
@ -31,9 +32,9 @@ public class LdapSession implements Closeable {
* outside of and be reused across all connections. We can't keep a static logger in this class * 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). * since we want the logger to be contextual (i.e. aware of the settings and its environment).
*/ */
public LdapSession(ESLogger logger, LDAPConnection connection, String boundName, GroupsResolver groupsResolver, TimeValue timeout) { public LdapSession(ESLogger logger, LDAPInterface connection, String boundName, GroupsResolver groupsResolver, TimeValue timeout) {
this.logger = logger; this.logger = logger;
this.ldapConnection = connection; this.ldap = connection;
this.bindDn = boundName; this.bindDn = boundName;
this.groupsResolver = groupsResolver; this.groupsResolver = groupsResolver;
this.timeout = timeout; this.timeout = timeout;
@ -44,7 +45,10 @@ public class LdapSession implements Closeable {
*/ */
@Override @Override
public void close() { public void close() {
ldapConnection.close(); // Only if it is an LDAPConnection do we need to close it
if (ldap instanceof LDAPConnection) {
((LDAPConnection) ldap).close();
}
} }
/** /**
@ -58,12 +62,12 @@ public class LdapSession implements Closeable {
* @return List of fully distinguished group names * @return List of fully distinguished group names
*/ */
public List<String> groups() { public List<String> groups() {
return groupsResolver.resolve(ldapConnection, bindDn, timeout, logger); return groupsResolver.resolve(ldap, bindDn, timeout, logger);
} }
public static interface GroupsResolver { public static interface GroupsResolver {
List<String> resolve(LDAPConnection ldapConnection, String userDn, TimeValue timeout, ESLogger logger); List<String> resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger);
} }
} }

View File

@ -39,16 +39,16 @@ public final class LdapUtils {
/** /**
* This method performs a LDAPConnection.search(...) operation while handling referral exceptions. This is necessary * This method performs a LDAPConnection.search(...) operation while handling referral exceptions. This is necessary
* to maintain backwards compatibility * to maintain backwards compatibility
* @param ldapConnection * @param ldap
* @param searchRequest * @param searchRequest
* @param logger * @param logger
* @return * @return
* @throws LDAPException * @throws LDAPException
*/ */
public static SearchResult search(LDAPConnection ldapConnection, SearchRequest searchRequest, ESLogger logger) throws LDAPException { public static SearchResult search(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException {
SearchResult results; SearchResult results;
try { try {
results = ldapConnection.search(searchRequest); results = ldap.search(searchRequest);
} catch (LDAPSearchException e) { } catch (LDAPSearchException e) {
if (e.getResultCode().equals(ResultCode.REFERRAL) && e.getSearchResult() != null) { if (e.getResultCode().equals(ResultCode.REFERRAL) && e.getSearchResult() != null) {
if (logger.isDebugEnabled()){ if (logger.isDebugEnabled()){
@ -65,16 +65,16 @@ public final class LdapUtils {
/** /**
* This method performs a LDAPConnection.searchForEntry(...) operation while handling referral exceptions. This is necessary * This method performs a LDAPConnection.searchForEntry(...) operation while handling referral exceptions. This is necessary
* to maintain backwards compatibility * to maintain backwards compatibility
* @param ldapConnection * @param ldap
* @param searchRequest * @param searchRequest
* @param logger * @param logger
* @return * @return
* @throws LDAPException * @throws LDAPException
*/ */
public static SearchResultEntry searchForEntry(LDAPConnection ldapConnection, SearchRequest searchRequest, ESLogger logger) throws LDAPException { public static SearchResultEntry searchForEntry(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException {
SearchResultEntry entry; SearchResultEntry entry;
try { try {
entry = ldapConnection.searchForEntry(searchRequest); entry = ldap.searchForEntry(searchRequest);
} catch (LDAPSearchException e) { } catch (LDAPSearchException e) {
if (e.getResultCode().equals(ResultCode.REFERRAL) && e.getSearchResult() != null && e.getSearchResult().getEntryCount() > 0) { if (e.getResultCode().equals(ResultCode.REFERRAL) && e.getSearchResult() != null && e.getSearchResult().getEntryCount() > 0) {
if (logger.isDebugEnabled()){ if (logger.isDebugEnabled()){

View File

@ -33,7 +33,7 @@ import static org.elasticsearch.common.collect.Iterables.all;
* A standard looking usage pattern could look like this: * A standard looking usage pattern could look like this:
<pre> <pre>
ConnectionFactory factory = ... ConnectionFactory factory = ...
try (LdapConnection session = factory.open(...)) { try (LdapConnection session = factory.session(...)) {
...do stuff with the session ...do stuff with the session
} }
</pre> </pre>
@ -74,7 +74,7 @@ public abstract class SessionFactory {
* @param user The name of the user to authenticate the connection with. * @param user The name of the user to authenticate the connection with.
* @param password The password of the user * @param password The password of the user
*/ */
public abstract LdapSession open(String user, SecuredString password); public abstract LdapSession session(String user, SecuredString password);
protected static LDAPConnectionOptions connectionOptions(Settings settings) { protected static LDAPConnectionOptions connectionOptions(Settings settings) {
LDAPConnectionOptions options = new LDAPConnectionOptions(); LDAPConnectionOptions options = new LDAPConnectionOptions();

View File

@ -59,7 +59,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
String userName = "ironman"; String userName = "ironman";
try (LdapSession ldap = sessionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
containsString("Geniuses"), containsString("Geniuses"),
@ -85,7 +85,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
RealmConfig config = new RealmConfig("ad-test", settings); RealmConfig config = new RealmConfig("ad-test", settings);
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
try (LdapSession ldap = sessionFactory.open("ironman", SecuredStringTests.build(PASSWORD))) { 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 // In certain cases we may have a successful bind, but a search should take longer and cause a timeout
ldap.groups(); ldap.groups();
fail("The TCP connection should timeout before getting groups back"); fail("The TCP connection should timeout before getting groups back");
@ -101,7 +101,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
for(String user: users) { for(String user: users) {
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) {
assertThat("group avenger test for user "+user, ldap.groups(), hasItem(Matchers.containsString("Avengers"))); assertThat("group avenger test for user "+user, ldap.groups(), hasItem(Matchers.containsString("Avengers")));
} }
} }
@ -114,7 +114,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
String userName = "hulk"; String userName = "hulk";
try (LdapSession ldap = sessionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
@ -135,7 +135,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
String userName = "hulk"; String userName = "hulk";
try (LdapSession ldap = sessionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
@ -160,7 +160,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
String userName = "hulk"; String userName = "hulk";
try (LdapSession ldap = sessionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, hasItem(containsString("Avengers"))); assertThat(groups, hasItem(containsString("Avengers")));
@ -175,7 +175,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
//Login with the UserPrincipalName //Login with the UserPrincipalName
String userDN; String userDN;
try (LdapSession ldap = sessionFactory.open("erik.selvig", SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session("erik.selvig", SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
userDN = ldap.authenticatedUserDn(); userDN = ldap.authenticatedUserDn();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
@ -184,7 +184,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
containsString("Domain Users"))); containsString("Domain Users")));
} }
//Same user but login with sAMAccountName //Same user but login with sAMAccountName
try (LdapSession ldap = sessionFactory.open("selvig", SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session("selvig", SecuredStringTests.build(PASSWORD))) {
assertThat(ldap.authenticatedUserDn(), is(userDN)); assertThat(ldap.authenticatedUserDn(), is(userDN));
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
@ -205,7 +205,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
//Login with the UserPrincipalName //Login with the UserPrincipalName
try (LdapSession ldap = sessionFactory.open("erik.selvig", SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session("erik.selvig", SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
containsString("Geniuses"), containsString("Geniuses"),
@ -224,7 +224,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
String user = "Bruce Banner"; String user = "Bruce Banner";
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
@ -243,7 +243,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
String user = "Bruce Banner"; String user = "Bruce Banner";
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder( assertThat(groups, containsInAnyOrder(
@ -260,7 +260,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService);
String userName = "ironman"; String userName = "ironman";
try (LdapSession ldap = sessionFactory.open(userName, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
} catch (ActiveDirectoryException e) { } catch (ActiveDirectoryException e) {
assertThat(e.getMessage(), containsString("failed to connect to any active directory servers")); assertThat(e.getMessage(), containsString("failed to connect to any active directory servers"));
@ -279,7 +279,7 @@ public class ActiveDirectorySessionFactoryTests extends ElasticsearchTestCase {
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
String user = "Bruce Banner"; String user = "Bruce Banner";
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
} }
} }

View File

@ -8,8 +8,10 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.User; import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.SessionFactory;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
@ -22,8 +24,10 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.Matchers.arrayContaining; import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.USER_DN_TEMPLATES_SETTING;
import static org.hamcrest.Matchers.notNullValue; import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.HOSTNAME_VERIFICATION_SETTING;
import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.URLS_SETTING;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -103,8 +107,8 @@ public class LdapRealmTest extends LdapTest {
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//verify one and only one open -> caching is working //verify one and only one session -> caching is working
verify(ldapFactory, times(1)).open(anyString(), any(SecuredString.class)); verify(ldapFactory, times(1)).session(anyString(), any(SecuredString.class));
} }
@Test @Test
@ -123,15 +127,15 @@ public class LdapRealmTest extends LdapTest {
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//verify one and only one open -> caching is working //verify one and only one session -> caching is working
verify(ldapFactory, times(1)).open(anyString(), any(SecuredString.class)); verify(ldapFactory, times(1)).session(anyString(), any(SecuredString.class));
roleMapper.notifyRefresh(); roleMapper.notifyRefresh();
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//we need to open again //we need to session again
verify(ldapFactory, times(2)).open(anyString(), any(SecuredString.class)); verify(ldapFactory, times(2)).session(anyString(), any(SecuredString.class));
} }
@Test @Test
@ -151,7 +155,62 @@ public class LdapRealmTest extends LdapTest {
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
//verify two and only two binds -> caching is disabled //verify two and only two binds -> caching is disabled
verify(ldapFactory, times(2)).open(anyString(), any(SecuredString.class)); verify(ldapFactory, times(2)).session(anyString(), any(SecuredString.class));
} }
@Test
public void testLdapRealmSelectsLdapSessionFactory() throws Exception {
String groupSearchBase = "o=sevenSeas";
String userTemplate = VALID_USER_TEMPLATE;
Settings settings = ImmutableSettings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(USER_DN_TEMPLATES_SETTING, userTemplate)
.put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", LdapSearchScope.SUB_TREE)
.put(HOSTNAME_VERIFICATION_SETTING, false)
.build();
RealmConfig config = new RealmConfig("test-ldap-realm", settings);
SessionFactory sessionFactory = LdapRealm.Factory.sessionFactory(config, null);
assertThat(sessionFactory, is(instanceOf(LdapSessionFactory.class)));
}
@Test
public void testLdapRealmSelectsLdapUserSearchSessionFactory() throws Exception {
String groupSearchBase = "o=sevenSeas";
Settings settings = ImmutableSettings.builder()
.putArray(URLS_SETTING, ldapUrl())
.put("user_search.base_dn", "")
.put("user_search.bind_dn", "cn=Thomas Masterman Hardy,ou=people,o=sevenSeas")
.put("user_search.bind_password", PASSWORD)
.put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", LdapSearchScope.SUB_TREE)
.put(HOSTNAME_VERIFICATION_SETTING, false)
.build();
RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings);
SessionFactory sessionFactory = LdapRealm.Factory.sessionFactory(config, null);
try {
assertThat(sessionFactory, is(instanceOf(LdapUserSearchSessionFactory.class)));
} finally {
((LdapUserSearchSessionFactory)sessionFactory).shutdown();
}
}
@Test
public void testLdapRealmThrowsExceptionForUserTemplateAndSearchSettings() throws Exception {
Settings settings = ImmutableSettings.builder()
.putArray(URLS_SETTING, ldapUrl())
.putArray(USER_DN_TEMPLATES_SETTING, "cn=foo")
.put("user_search.base_dn", "cn=bar")
.put("group_search.base_dn", "")
.put("group_search.scope", LdapSearchScope.SUB_TREE)
.put(HOSTNAME_VERIFICATION_SETTING, false)
.build();
RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings);
try {
LdapRealm.Factory.sessionFactory(config, null);
fail("an exception should have been thrown because both user template and user search settings were specified");
} catch (ShieldSettingsException e) {
assertThat(e.getMessage(), containsString("settings were found for both user search and user template"));
}
}
} }

View File

@ -42,7 +42,7 @@ public class LdapSessionFactoryTests extends LdapTest {
ldapServer.setProcessingDelayMillis(500L); ldapServer.setProcessingDelayMillis(500L);
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
try (LdapSession session = sessionFactory.open(user, userPass)) { try (LdapSession session = sessionFactory.session(user, userPass)) {
fail("expected connection timeout error here"); fail("expected connection timeout error here");
} catch (Throwable t) { } catch (Throwable t) {
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
@ -73,7 +73,7 @@ public class LdapSessionFactoryTests extends LdapTest {
SecuredString userPass = SecuredStringTests.build("pass"); SecuredString userPass = SecuredStringTests.build("pass");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
try (LdapSession session = sessionFactory.open(user, userPass)) { try (LdapSession session = sessionFactory.session(user, userPass)) {
fail("expected connection timeout error here"); fail("expected connection timeout error here");
} catch (Throwable t) { } catch (Throwable t) {
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
@ -98,7 +98,7 @@ public class LdapSessionFactoryTests extends LdapTest {
String user = "Horatio Hornblower"; String user = "Horatio Hornblower";
SecuredString userPass = SecuredStringTests.build("pass"); SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.open(user, userPass)) { try (LdapSession ldap = sessionFactory.session(user, userPass)) {
String dn = ldap.authenticatedUserDn(); String dn = ldap.authenticatedUserDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
@ -119,7 +119,7 @@ public class LdapSessionFactoryTests extends LdapTest {
String user = "Horatio Hornblower"; String user = "Horatio Hornblower";
SecuredString userPass = SecuredStringTests.build("pass"); SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldapConnection = ldapFac.open(user, userPass)) { try (LdapSession ldapConnection = ldapFac.session(user, userPass)) {
} }
} }
@ -134,7 +134,7 @@ public class LdapSessionFactoryTests extends LdapTest {
String user = "Horatio Hornblower"; String user = "Horatio Hornblower";
SecuredString userPass = SecuredStringTests.build("pass"); SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = ldapFac.open(user, userPass)) { try (LdapSession ldap = ldapFac.session(user, userPass)) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas")); assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));
} }
@ -149,7 +149,7 @@ public class LdapSessionFactoryTests extends LdapTest {
LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); LdapSessionFactory ldapFac = new LdapSessionFactory(config, null);
String user = "Horatio Hornblower"; String user = "Horatio Hornblower";
try (LdapSession ldap = ldapFac.open(user, SecuredStringTests.build("pass"))) { try (LdapSession ldap = ldapFac.session(user, SecuredStringTests.build("pass"))) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas")); assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));
} }
@ -166,7 +166,7 @@ public class LdapSessionFactoryTests extends LdapTest {
String user = "Horatio Hornblower"; String user = "Horatio Hornblower";
SecuredString userPass = SecuredStringTests.build("pass"); SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = ldapFac.open(user, userPass)) { try (LdapSession ldap = ldapFac.session(user, userPass)) {
List<String> groups = ldap.groups(); List<String> groups = ldap.groups();
assertThat(groups.size(), is(1)); assertThat(groups.size(), is(1));
assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas")); assertThat(groups, containsInAnyOrder("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));

View File

@ -0,0 +1,375 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.ldap;
import com.unboundid.ldap.sdk.*;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.activedirectory.ActiveDirectorySessionFactoryTests;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.authc.ldap.support.LdapSession;
import org.elasticsearch.shield.authc.ldap.support.LdapTest;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.ssl.ClientSSLService;
import org.junit.Before;
import org.junit.Test;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.List;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.Matchers.*;
public class LdapUserSearchSessionFactoryTests extends LdapTest {
private ClientSSLService clientSSLService;
@Before
public void initializeSslSocketFactory() throws Exception {
Path keystore = Paths.get(getResource("support/ldaptrust.jks").toURI()).toAbsolutePath();
/*
* Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext.
* If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname
* verification tests since a re-established connection does not perform hostname verification.
*/
clientSSLService = new ClientSSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", keystore)
.put("shield.ssl.keystore.password", "changeit")
.build());
}
@Test
public void testUserSearchSubTree() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.attribute", "cn")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
String dn = ldap.authenticatedUserDn();
assertThat(dn, containsString(user));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchBaseScopeFailsWithWrongBaseDN() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.scope", LdapSearchScope.BASE)
.put("user_search.attribute", "cn")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
fail("the user should not have been found");
} catch (ShieldLdapException e) {
assertThat(e.getMessage(), containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [base]"));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchBaseScopePassesWithCorrectBaseDN() throws Exception {
String groupSearchBase = "o=sevenSeas";
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.scope", LdapSearchScope.BASE)
.put("user_search.attribute", "cn")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
String dn = ldap.authenticatedUserDn();
assertThat(dn, containsString(user));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchOneLevelScopeFailsWithWrongBaseDN() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.scope", LdapSearchScope.ONE_LEVEL)
.put("user_search.attribute", "cn")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
fail("the user should not have been found");
} catch (ShieldLdapException e) {
assertThat(e.getMessage(), containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [one_level]"));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchOneLevelScopePassesWithCorrectBaseDN() throws Exception {
String groupSearchBase = "o=sevenSeas";
String userSearchBase = "ou=people,o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.scope", LdapSearchScope.ONE_LEVEL)
.put("user_search.attribute", "cn")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
String dn = ldap.authenticatedUserDn();
assertThat(dn, containsString(user));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchWithBadAttributeFails() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.attribute", "uid1")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "William Bush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
fail("the user should not have been found");
} catch (ShieldLdapException e) {
assertThat(e.getMessage(), containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [sub_tree]"));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchWithoutAttributePasses() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
String user = "wbush";
SecuredString userPass = SecuredStringTests.build("pass");
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
String dn = ldap.authenticatedUserDn();
assertThat(dn, containsString("William Bush"));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchWithActiveDirectory() {
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(LdapTest.buildLdapSettings(ActiveDirectorySessionFactoryTests.AD_LDAP_URL, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "ironman@ad.test.elasticsearch.com")
.put("user_search.bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
.put("user_search.attribute", "cn")
.build();
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings);
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService);
String user = "Bruce Banner";
try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(ActiveDirectorySessionFactoryTests.PASSWORD))) {
List<String> groups = ldap.groups();
assertThat(groups, containsInAnyOrder(
containsString("Avengers"),
containsString("SHIELD"),
containsString("Geniuses"),
containsString("Philanthropists")));
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testUserSearchwithBindUserOpenLDAP() {
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(LdapTest.buildLdapSettings(OpenLdapTests.OPEN_LDAP_URL, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.put("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "uid=blackwidow,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com")
.put("user_search.bind_password", OpenLdapTests.PASSWORD)
.build());
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService);
String[] users = new String[] { "cap", "hawkeye", "hulk", "ironman", "thor" };
try {
for (String user : users) {
LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(OpenLdapTests.PASSWORD));
assertThat(ldap.authenticatedUserDn(), is(equalTo(MessageFormat.format("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", user))));
assertThat(ldap.groups(), hasItem(containsString("Avengers")));
ldap.close();
}
} finally {
sessionFactory.shutdown();
}
}
@Test
public void testConnectionPoolDefaultSettings() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.build());
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.connectionPool(config.settings(), new SingleServerSet("localhost", ldapServer.getListenPort()), TimeValue.timeValueSeconds(5));
try {
assertThat(connectionPool.getCurrentAvailableConnections(), is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_INITIAL_SIZE));
assertThat(connectionPool.getMaximumAvailableConnections(), is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_SIZE));
assertEquals(connectionPool.getHealthCheck().getClass(), GetEntryLDAPConnectionPoolHealthCheck.class);
GetEntryLDAPConnectionPoolHealthCheck healthCheck = (GetEntryLDAPConnectionPoolHealthCheck) connectionPool.getHealthCheck();
assertThat(healthCheck.getEntryDN(), is("cn=Horatio Hornblower,ou=people,o=sevenSeas"));
assertThat(healthCheck.getMaxResponseTimeMillis(), is(LdapUserSearchSessionFactory.TIMEOUT_DEFAULT.millis()));
} finally {
connectionPool.close();
}
}
@Test
public void testConnectionPoolSettings() throws Exception {
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("user_search.bind_password", "pass")
.put("user_search.pool.initial_size", 10)
.put("user_search.pool.size", 12)
.put("user_search.pool.health_check.enabled", false)
.build());
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.connectionPool(config.settings(), new SingleServerSet("localhost", ldapServer.getListenPort()), TimeValue.timeValueSeconds(5));
try {
assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
assertThat(connectionPool.getMaximumAvailableConnections(), is(12));
assertThat(connectionPool.retryFailedOperationsDueToInvalidConnections(), is(true));
assertEquals(connectionPool.getHealthCheck().getClass(), LDAPConnectionPoolHealthCheck.class);
} finally {
connectionPool.close();
}
}
@Test
public void testThatEmptyBindDNThrowsExceptionWithHealthCheckEnabled() throws Exception{
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("user_search.base_dn", userSearchBase)
.put("user_search.bind_password", "pass")
.build());
try {
new LdapUserSearchSessionFactory(config, null);
} catch (ShieldSettingsException e) {
assertThat(e.getMessage(), containsString("[user_search.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"));
}
}
@Test
public void testEmptyBindDNReturnsNullBindRequest() {
BindRequest request = LdapUserSearchSessionFactory.bindRequest(settingsBuilder().put("user_search.bind_password", "password").build());
assertThat(request, is(nullValue()));
}
@Test
public void testThatBindRequestReturnsSimpleBindRequest() {
BindRequest request = LdapUserSearchSessionFactory.bindRequest(settingsBuilder()
.put("user_search.bind_password", "password")
.put("user_search.bind_dn", "cn=ironman")
.build());
assertEquals(request.getClass(), SimpleBindRequest.class);
SimpleBindRequest simpleBindRequest = (SimpleBindRequest) request;
assertThat(simpleBindRequest.getBindDN(), is("cn=ironman"));
}
}

View File

@ -23,8 +23,7 @@ import org.junit.Test;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.hasItem;
@Network @Network
public class OpenLdapTests extends ElasticsearchTestCase { public class OpenLdapTests extends ElasticsearchTestCase {
@ -59,7 +58,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" };
for (String user : users) { for (String user : users) {
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) {
assertThat(ldap.groups(), hasItem(containsString("Avengers"))); assertThat(ldap.groups(), hasItem(containsString("Avengers")));
} }
} }
@ -76,7 +75,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" };
for (String user : users) { for (String user : users) {
LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD)); LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD));
assertThat(ldap.groups(), hasItem(containsString("Avengers"))); assertThat(ldap.groups(), hasItem(containsString("Avengers")));
ldap.close(); ldap.close();
} }
@ -94,7 +93,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
RealmConfig config = new RealmConfig("oldap-test", settings); RealmConfig config = new RealmConfig("oldap-test", settings);
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
try (LdapSession ldap = sessionFactory.open("selvig", SecuredStringTests.build(PASSWORD))){ try (LdapSession ldap = sessionFactory.session("selvig", SecuredStringTests.build(PASSWORD))){
assertThat(ldap.groups(), hasItem(containsString("Geniuses"))); assertThat(ldap.groups(), hasItem(containsString("Geniuses")));
} }
} }
@ -112,7 +111,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
RealmConfig config = new RealmConfig("oldap-test", settings); RealmConfig config = new RealmConfig("oldap-test", settings);
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
try (LdapSession ldap = sessionFactory.open("thor", SecuredStringTests.build(PASSWORD))) { 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 // In certain cases we may have a successful bind, but a search should take longer and cause a timeout
ldap.groups(); ldap.groups();
fail("The TCP connection should timeout before getting groups back"); fail("The TCP connection should timeout before getting groups back");
@ -135,7 +134,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService);
String user = "blackwidow"; String user = "blackwidow";
try (LdapSession ldap = sessionFactory.open(user, SecuredStringTests.build(PASSWORD))) { 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"); fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open");
} catch (ShieldLdapException e) { } catch (ShieldLdapException e) {
assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers"));

View File

@ -52,7 +52,7 @@ public class SessionFactoryTests extends ElasticsearchTestCase {
return new SessionFactory(new RealmConfig("_name")) { return new SessionFactory(new RealmConfig("_name")) {
@Override @Override
public LdapSession open(String user, SecuredString password) { public LdapSession session(String user, SecuredString password) {
return null; return null;
} }
}; };