Failed LDAP SearchResults should be Exceptions (elastic/x-pack-elasticsearch#773)
When the LDAP SDK returns a SearchResult that has a non-success ResultCode, convert it to an exception and call onFailure A configuration setting controls whether failures in referrals should be fatal (defaults to ignoring errors) Closes: elastic/x-pack-elasticsearch#717 Original commit: elastic/x-pack-elasticsearch@4159758c2a
This commit is contained in:
parent
7c45cb7ccf
commit
7f0fd9e1a3
|
@ -475,12 +475,9 @@
|
|||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapRealm.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapSessionFactory.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]LdapUserSearchSessionFactory.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]SearchGroupsResolver.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]UserAttributeGroupsResolver.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapLoadBalancing.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapSession.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]LdapUtils.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]ldap[/\\]support[/\\]SessionFactory.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]pki[/\\]PkiRealm.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]BCrypt.java" checks="LineLength" />
|
||||
<suppress files="plugin[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]security[/\\]authc[/\\]support[/\\]CachingRealm.java" checks="LineLength" />
|
||||
|
|
|
@ -33,22 +33,26 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
|||
private static final String TOKEN_GROUPS = "tokenGroups";
|
||||
private final String baseDn;
|
||||
private final LdapSearchScope scope;
|
||||
private final boolean ignoreReferralErrors;
|
||||
|
||||
ActiveDirectoryGroupsResolver(Settings settings, String baseDnDefault) {
|
||||
ActiveDirectoryGroupsResolver(Settings settings, String baseDnDefault,
|
||||
boolean ignoreReferralErrors) {
|
||||
this.baseDn = settings.get("base_dn", baseDnDefault);
|
||||
this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE);
|
||||
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(LDAPInterface connection, String userDn, TimeValue timeout, Logger logger, Collection<Attribute> attributes,
|
||||
ActionListener<List<String>> listener) {
|
||||
buildGroupQuery(connection, userDn, timeout,
|
||||
ActionListener.wrap((filter) -> {
|
||||
ignoreReferralErrors, ActionListener.wrap((filter) -> {
|
||||
if (filter == null) {
|
||||
listener.onResponse(Collections.emptyList());
|
||||
} else {
|
||||
logger.debug("group SID to DN [{}] search filter: [{}]", userDn, filter);
|
||||
search(connection, baseDn, scope.scope(), filter, Math.toIntExact(timeout.seconds()),
|
||||
search(connection, baseDn, scope.scope(), filter,
|
||||
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||
ActionListener.wrap((results) -> {
|
||||
List<String> groups = results.stream()
|
||||
.map(SearchResultEntry::getDN)
|
||||
|
@ -67,8 +71,10 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
static void buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout, ActionListener<Filter> listener) {
|
||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
||||
static void buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout,
|
||||
boolean ignoreReferralErrors, ActionListener<Filter> listener) {
|
||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER,
|
||||
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||
ActionListener.wrap((entry) -> {
|
||||
if (entry == null || entry.hasAttribute(TOKEN_GROUPS) == false) {
|
||||
listener.onResponse(null);
|
||||
|
|
|
@ -65,13 +65,18 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
Settings settings = config.settings();
|
||||
String domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
||||
if (domainName == null) {
|
||||
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory");
|
||||
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING +
|
||||
"] setting for active directory");
|
||||
}
|
||||
String domainDN = buildDnFromDomain(domainName);
|
||||
GroupsResolver groupResolver = new ActiveDirectoryGroupsResolver(settings.getAsSettings("group_search"), domainDN);
|
||||
defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout, logger, groupResolver, domainDN);
|
||||
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, logger, groupResolver, domainDN, sslService);
|
||||
upnADAuthenticator = new UpnADAuthenticator(settings, timeout, logger, groupResolver, domainDN);
|
||||
GroupsResolver groupResolver = new ActiveDirectoryGroupsResolver(
|
||||
settings.getAsSettings("group_search"), domainDN, ignoreReferralErrors);
|
||||
defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout,
|
||||
ignoreReferralErrors, logger, groupResolver, domainDN);
|
||||
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout,
|
||||
ignoreReferralErrors, logger, groupResolver, domainDN, sslService);
|
||||
upnADAuthenticator = new UpnADAuthenticator(settings, timeout,
|
||||
ignoreReferralErrors, logger, groupResolver, domainDN);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -135,13 +140,16 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
abstract static class ADAuthenticator {
|
||||
|
||||
final TimeValue timeout;
|
||||
final boolean ignoreReferralErrors;
|
||||
final Logger logger;
|
||||
final GroupsResolver groupsResolver;
|
||||
final String userSearchDN;
|
||||
final LdapSearchScope userSearchScope;
|
||||
|
||||
ADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
ADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
this.timeout = timeout;
|
||||
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||
this.logger = logger;
|
||||
this.groupsResolver = groupsResolver;
|
||||
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
||||
|
@ -195,19 +203,22 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
final String userSearchFilter;
|
||||
|
||||
final String domainName;
|
||||
DefaultADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
super(settings, timeout, logger, groupsResolver, domainDN);
|
||||
DefaultADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN);
|
||||
domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
||||
userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" +
|
||||
"(userPrincipalName={0}@" + domainName + ")))");
|
||||
}
|
||||
|
||||
@Override
|
||||
void searchForDN(LDAPConnection connection, String username, SecuredString password, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener) {
|
||||
void searchForDN(LDAPConnection connection, String username, SecuredString password,
|
||||
int timeLimitSeconds, ActionListener<SearchResultEntry> listener) {
|
||||
try {
|
||||
searchForEntry(connection, userSearchDN, userSearchScope.scope(), createFilter(userSearchFilter, username),
|
||||
timeLimitSeconds, listener, attributesToSearchFor(groupsResolver.attributes()));
|
||||
searchForEntry(connection, userSearchDN, userSearchScope.scope(),
|
||||
createFilter(userSearchFilter, username), timeLimitSeconds,
|
||||
ignoreReferralErrors, listener,
|
||||
attributesToSearchFor(groupsResolver.attributes()));
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
|
@ -220,8 +231,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* Active Directory calls the format <code>DOMAIN\\username</code> down-level credentials and this class contains the logic necessary
|
||||
* to authenticate this form of a username
|
||||
* Active Directory calls the format <code>DOMAIN\\username</code> down-level credentials and
|
||||
* this class contains the logic necessary to authenticate this form of a username
|
||||
*/
|
||||
static class DownLevelADAuthenticator extends ADAuthenticator {
|
||||
Cache<String, String> domainNameCache = CacheBuilder.<String, String>builder().setMaximumWeight(100).build();
|
||||
|
@ -231,9 +242,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
final SSLService sslService;
|
||||
final RealmConfig config;
|
||||
|
||||
DownLevelADAuthenticator(RealmConfig config, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN,
|
||||
DownLevelADAuthenticator(RealmConfig config, TimeValue timeout,
|
||||
boolean ignoreReferralErrors, Logger logger,
|
||||
GroupsResolver groupsResolver, String domainDN,
|
||||
SSLService sslService) {
|
||||
super(config.settings(), timeout, logger, groupsResolver, domainDN);
|
||||
super(config.settings(), timeout, ignoreReferralErrors, logger, groupsResolver,
|
||||
domainDN);
|
||||
this.domainDN = domainDN;
|
||||
this.settings = config.settings();
|
||||
this.sslService = sslService;
|
||||
|
@ -255,8 +269,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
} else {
|
||||
try {
|
||||
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(),
|
||||
createFilter("(&(objectClass=user)(sAMAccountName={0}))", accountName), timeLimitSeconds, listener,
|
||||
attributesToSearchFor(groupsResolver.attributes()));
|
||||
createFilter("(&(objectClass=user)(sAMAccountName={0}))",
|
||||
accountName), timeLimitSeconds, ignoreReferralErrors,
|
||||
listener, attributesToSearchFor(groupsResolver.attributes()));
|
||||
} catch (LDAPException e) {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
|
@ -274,8 +289,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
if (cachedName != null) {
|
||||
listener.onResponse(cachedName);
|
||||
} else if (usingGlobalCatalog(settings, connection)) {
|
||||
// the global catalog does not replicate the necessary information to map a netbios dns name to a DN so we need to instead
|
||||
// connect to the normal ports. This code uses the standard ports to avoid adding even more settings and is probably ok as
|
||||
// the global catalog does not replicate the necessary information to map a netbios
|
||||
// dns name to a DN so we need to instead connect to the normal ports. This code
|
||||
// uses the standard ports to avoid adding even more settings and is probably ok as
|
||||
// most AD users do not use non-standard ports
|
||||
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
||||
boolean startedSearching = false;
|
||||
|
@ -283,7 +299,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
try {
|
||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||
if (connection.getSSLSession() != null) {
|
||||
searchConnection = LdapUtils.privilegedConnect(() -> new LDAPConnection(connection.getSocketFactory(), options,
|
||||
searchConnection = LdapUtils.privilegedConnect(
|
||||
() -> new LDAPConnection(connection.getSocketFactory(), options,
|
||||
connection.getConnectedAddress(), 636));
|
||||
} else {
|
||||
searchConnection = LdapUtils.privilegedConnect(() ->
|
||||
|
@ -291,10 +308,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
searchConnection.bind(username, new String(password.internalChars()));
|
||||
final LDAPConnection finalConnection = searchConnection;
|
||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
|
||||
ActionListener.wrap((results) -> {
|
||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||
(results) -> {
|
||||
IOUtils.close(finalConnection);
|
||||
handleSearchResults(results, netBiosDomainName, domainNameCache, listener);
|
||||
handleSearchResults(results, netBiosDomainName,
|
||||
domainNameCache, listener);
|
||||
}, (e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
|
@ -311,8 +330,10 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
} else {
|
||||
try {
|
||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||
search(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds,
|
||||
ActionListener.wrap((results) -> handleSearchResults(results, netBiosDomainName, domainNameCache, listener),
|
||||
search(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||
(results) -> handleSearchResults(results, netBiosDomainName,
|
||||
domainNameCache, listener),
|
||||
(e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
|
@ -324,9 +345,12 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName, Cache<String, String> domainNameCache,
|
||||
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName,
|
||||
Cache<String, String> domainNameCache,
|
||||
ActionListener<String> listener) {
|
||||
Optional<SearchResultEntry> entry = results.stream().filter((r) -> r.hasAttribute("ncname")).findFirst();
|
||||
Optional<SearchResultEntry> entry = results.stream()
|
||||
.filter((r) -> r.hasAttribute("ncname"))
|
||||
.findFirst();
|
||||
if (entry.isPresent()) {
|
||||
final String value = entry.get().getAttributeValue("ncname");
|
||||
try {
|
||||
|
@ -353,8 +377,9 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
|
||||
private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))";
|
||||
|
||||
UpnADAuthenticator(Settings settings, TimeValue timeout, Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
super(settings, timeout, logger, groupsResolver, domainDN);
|
||||
UpnADAuthenticator(Settings settings, TimeValue timeout, boolean ignoreReferralErrors,
|
||||
Logger logger, GroupsResolver groupsResolver, String domainDN) {
|
||||
super(settings, timeout, ignoreReferralErrors, logger, groupsResolver, domainDN);
|
||||
}
|
||||
|
||||
void searchForDN(LDAPConnection connection, String username, SecuredString password, int timeLimitSeconds,
|
||||
|
@ -366,7 +391,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
final String domainDN = buildDnFromDomain(domainName);
|
||||
try {
|
||||
Filter filter = createFilter(UPN_USER_FILTER, accountName, username);
|
||||
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter, timeLimitSeconds, listener,
|
||||
searchForEntry(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, listener,
|
||||
attributesToSearchFor(groupsResolver.attributes()));
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
|
|
|
@ -16,7 +16,6 @@ import com.unboundid.ldap.sdk.ServerSet;
|
|||
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.elasticsearch.SpecialPermission;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
@ -294,7 +293,8 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
|
||||
private void findUser(String user, LDAPInterface ldapInterface, ActionListener<SearchResultEntry> listener) {
|
||||
searchForEntry(ldapInterface, userSearchBaseDn, scope.scope(),
|
||||
createEqualityFilter(userAttribute, encodeValue(user)), Math.toIntExact(timeout.seconds()), listener,
|
||||
createEqualityFilter(userAttribute, encodeValue(user)),
|
||||
Math.toIntExact(timeout.seconds()), ignoreReferralErrors, listener,
|
||||
attributesToSearchFor(groupResolver.attributes()));
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -33,29 +34,34 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJE
|
|||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING;
|
||||
|
||||
/**
|
||||
* Resolves the groups for a user by executing a search with a filter usually that contains a group object class with a attribute that
|
||||
* matches an ID of the user
|
||||
* Resolves the groups for a user by executing a search with a filter usually that contains a group
|
||||
* object class with a attribute that matches an ID of the user
|
||||
*/
|
||||
class SearchGroupsResolver implements GroupsResolver {
|
||||
|
||||
private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" +
|
||||
"(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" +
|
||||
"(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)" +
|
||||
"(objectclass=group)(objectclass=posixGroup))" +
|
||||
"(|(uniqueMember={0})(member={0})(memberUid={0})))";
|
||||
|
||||
static final Setting<String> BASE_DN = Setting.simpleString("group_search.base_dn", Setting.Property.NodeScope);
|
||||
static final Setting<String> BASE_DN = Setting.simpleString("group_search.base_dn",
|
||||
Setting.Property.NodeScope);
|
||||
static final Setting<LdapSearchScope> SCOPE = new Setting<>("group_search.scope", (String) null,
|
||||
s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope);
|
||||
static final Setting<String> USER_ATTRIBUTE = Setting.simpleString("group_search.user_attribute", Setting.Property.NodeScope);
|
||||
static final Setting<String> USER_ATTRIBUTE = Setting.simpleString(
|
||||
"group_search.user_attribute", Setting.Property.NodeScope);
|
||||
|
||||
static final Setting<String> FILTER = new Setting<>("group_search.filter", GROUP_SEARCH_DEFAULT_FILTER,
|
||||
Function.identity(), Setting.Property.NodeScope);
|
||||
static final Setting<String> FILTER = new Setting<>("group_search.filter",
|
||||
GROUP_SEARCH_DEFAULT_FILTER, Function.identity(), Setting.Property.NodeScope);
|
||||
|
||||
private final String baseDn;
|
||||
private final String filter;
|
||||
private final String userAttribute;
|
||||
private final LdapSearchScope scope;
|
||||
private final boolean ignoreReferralErrors;
|
||||
|
||||
SearchGroupsResolver(Settings settings) {
|
||||
if (BASE_DN.exists(settings)) {
|
||||
|
@ -66,6 +72,7 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||
filter = FILTER.get(settings);
|
||||
userAttribute = USER_ATTRIBUTE.get(settings);
|
||||
scope = SCOPE.get(settings);
|
||||
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,9 +84,14 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||
} else {
|
||||
try {
|
||||
Filter userFilter = createFilter(filter, userId);
|
||||
search(connection, baseDn, scope.scope(), userFilter, Math.toIntExact(timeout.seconds()),
|
||||
search(connection, baseDn, scope.scope(), userFilter,
|
||||
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||
ActionListener.wrap(
|
||||
(results) -> listener.onResponse(results.stream().map((r) -> r.getDN()).collect(Collectors.toList())),
|
||||
(results) -> listener.onResponse(results
|
||||
.stream()
|
||||
.map((r) -> r.getDN())
|
||||
.collect(Collectors.toList())
|
||||
),
|
||||
listener::onFailure),
|
||||
SearchRequest.NO_ATTRIBUTES);
|
||||
} catch (LDAPException e) {
|
||||
|
@ -96,12 +108,13 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void getUserId(String dn, Collection<Attribute> attributes, LDAPInterface connection, TimeValue timeout,
|
||||
ActionListener<String> listener) {
|
||||
private void getUserId(String dn, Collection<Attribute> attributes, LDAPInterface connection,
|
||||
TimeValue timeout, ActionListener<String> listener) {
|
||||
if (isNullOrEmpty(userAttribute)) {
|
||||
listener.onResponse(dn);
|
||||
} else if (attributes != null) {
|
||||
final String value = attributes.stream().filter((attribute) -> attribute.getName().equals(userAttribute))
|
||||
final String value = attributes.stream()
|
||||
.filter((attribute) -> attribute.getName().equals(userAttribute))
|
||||
.map(Attribute::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
@ -111,8 +124,10 @@ class SearchGroupsResolver implements GroupsResolver {
|
|||
}
|
||||
}
|
||||
|
||||
void readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ActionListener<String> listener) {
|
||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
||||
void readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout,
|
||||
ActionListener<String> listener) {
|
||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER,
|
||||
Math.toIntExact(timeout.seconds()), ignoreReferralErrors,
|
||||
ActionListener.wrap((entry) -> {
|
||||
if (entry == null || entry.hasAttribute(userAttribute) == false) {
|
||||
listener.onResponse(null);
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.function.Function;
|
|||
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING;
|
||||
|
||||
/**
|
||||
* Resolves the groups of a user based on the value of a attribute of the user's ldap entry
|
||||
|
@ -35,13 +36,15 @@ class UserAttributeGroupsResolver implements GroupsResolver {
|
|||
private static final Setting<String> ATTRIBUTE = new Setting<>("user_group_attribute", "memberOf",
|
||||
Function.identity(), Setting.Property.NodeScope);
|
||||
private final String attribute;
|
||||
private final boolean ignoreReferralErrors;
|
||||
|
||||
UserAttributeGroupsResolver(Settings settings) {
|
||||
this(ATTRIBUTE.get(settings));
|
||||
this(ATTRIBUTE.get(settings), IGNORE_REFERRAL_ERRORS_SETTING.get(settings));
|
||||
}
|
||||
|
||||
private UserAttributeGroupsResolver(String attribute) {
|
||||
private UserAttributeGroupsResolver(String attribute, boolean ignoreReferralErrors) {
|
||||
this.attribute = Objects.requireNonNull(attribute);
|
||||
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,7 +56,7 @@ class UserAttributeGroupsResolver implements GroupsResolver {
|
|||
listener.onResponse(Collections.unmodifiableList(list));
|
||||
} else {
|
||||
searchForEntry(connection, userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, Math.toIntExact(timeout.seconds()),
|
||||
ActionListener.wrap((entry) -> {
|
||||
ignoreReferralErrors, ActionListener.wrap((entry) -> {
|
||||
if (entry == null || entry.hasAttribute(attribute) == false) {
|
||||
listener.onResponse(Collections.emptyList());
|
||||
} else {
|
||||
|
|
|
@ -44,12 +44,14 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class LdapUtils {
|
||||
|
||||
public static final Filter OBJECT_CLASS_PRESENCE_FILTER = Filter.createPresenceFilter("objectClass");
|
||||
public static final Filter OBJECT_CLASS_PRESENCE_FILTER =
|
||||
Filter.createPresenceFilter("objectClass");
|
||||
|
||||
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
||||
|
||||
private LdapUtils() {
|
||||
}
|
||||
|
@ -62,7 +64,8 @@ public final class LdapUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static <T> T privilegedConnect(CheckedSupplier<T, LDAPException> supplier) throws LDAPException {
|
||||
public static <T> T privilegedConnect(CheckedSupplier<T, LDAPException> supplier)
|
||||
throws LDAPException {
|
||||
SpecialPermission.check();
|
||||
try {
|
||||
return AccessController.doPrivileged((PrivilegedExceptionAction<T>) supplier::get);
|
||||
|
@ -83,27 +86,40 @@ public final class LdapUtils {
|
|||
/**
|
||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||
*/
|
||||
public static void searchForEntry(LDAPInterface ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
||||
public static void searchForEntry(LDAPInterface ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<SearchResultEntry> listener,
|
||||
String... attributes) {
|
||||
if (ldap instanceof LDAPConnection) {
|
||||
searchForEntry((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
||||
searchForEntry((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||
ignoreReferralErrors, listener, attributes);
|
||||
} else if (ldap instanceof LDAPConnectionPool) {
|
||||
searchForEntry((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
||||
searchForEntry((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||
ignoreReferralErrors, listener, attributes);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs an asynchronous ldap search operation that only expects at most one result. If more than one result is found
|
||||
* then this is an error. If no results are found, then {@code null} will be returned.
|
||||
* This method performs an asynchronous ldap search operation that only expects at most one
|
||||
* result.
|
||||
* If more than one result is found then this is an error
|
||||
* If no results are found, then {@code null} will be returned.
|
||||
* If the LDAP server returns an error {@link ResultCode} then this is handled as a
|
||||
* {@link ActionListener#onFailure(Exception) failure}
|
||||
*/
|
||||
public static void searchForEntry(LDAPConnection ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
||||
LdapSearchResultListener searchResultListener = new SingleEntryListener(ldap, listener, filter);
|
||||
public static void searchForEntry(LDAPConnection ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<SearchResultEntry> listener,
|
||||
String... attributes) {
|
||||
LdapSearchResultListener searchResultListener = new SingleEntryListener(ldap, listener,
|
||||
filter, ignoreReferralErrors);
|
||||
try {
|
||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
||||
false, filter, attributes);
|
||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope,
|
||||
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||
searchResultListener.setSearchRequest(request);
|
||||
ldap.asyncSearch(request);
|
||||
} catch (LDAPException e) {
|
||||
|
@ -112,23 +128,33 @@ public final class LdapUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method performs an asynchronous ldap search operation that only expects at most one result. If more than one result is found
|
||||
* then this is an error. If no results are found, then {@code null} will be returned.
|
||||
* This method performs an asynchronous ldap search operation that only expects at most one
|
||||
* result.
|
||||
* If more than one result is found then this is an error.
|
||||
* If no results are found, then {@code null} will be returned.
|
||||
* If the LDAP server returns an error {@link ResultCode} then this is handled as a
|
||||
* {@link ActionListener#onFailure(Exception) failure}
|
||||
*/
|
||||
public static void searchForEntry(LDAPConnectionPool ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener, String... attributes) {
|
||||
public static void searchForEntry(LDAPConnectionPool ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<SearchResultEntry> listener,
|
||||
String... attributes) {
|
||||
boolean searching = false;
|
||||
LDAPConnection ldapConnection = null;
|
||||
try {
|
||||
ldapConnection = privilegedConnect(ldap::getConnection);
|
||||
final LDAPConnection finalConnection = ldapConnection;
|
||||
searchForEntry(finalConnection, baseDN, scope, filter, timeLimitSeconds, ActionListener.wrap(
|
||||
searchForEntry(finalConnection, baseDN, scope, filter, timeLimitSeconds,
|
||||
ignoreReferralErrors, ActionListener.wrap(
|
||||
(entry) -> {
|
||||
IOUtils.close(() -> ldap.releaseConnection(finalConnection));
|
||||
listener.onResponse(entry);
|
||||
},
|
||||
(e) -> {
|
||||
IOUtils.closeWhileHandlingException(() -> ldap.releaseConnection(finalConnection));
|
||||
IOUtils.closeWhileHandlingException(
|
||||
() -> ldap.releaseConnection(finalConnection)
|
||||
);
|
||||
listener.onFailure(e);
|
||||
}), attributes);
|
||||
searching = true;
|
||||
|
@ -145,12 +171,17 @@ public final class LdapUtils {
|
|||
/**
|
||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||
*/
|
||||
public static void search(LDAPInterface ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
||||
public static void search(LDAPInterface ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<List<SearchResultEntry>> listener,
|
||||
String... attributes) {
|
||||
if (ldap instanceof LDAPConnection) {
|
||||
search((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
||||
search((LDAPConnection) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||
ignoreReferralErrors, listener, attributes);
|
||||
} else if (ldap instanceof LDAPConnectionPool) {
|
||||
search((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds, listener, attributes);
|
||||
search((LDAPConnectionPool) ldap, baseDN, scope, filter, timeLimitSeconds,
|
||||
ignoreReferralErrors, listener, attributes);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported LDAPInterface implementation: " + ldap);
|
||||
}
|
||||
|
@ -159,14 +190,23 @@ public final class LdapUtils {
|
|||
/**
|
||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||
*/
|
||||
public static void search(LDAPConnection ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
||||
LdapSearchResultListener searchResultListener = new LdapSearchResultListener(ldap,
|
||||
(asyncRequestID, searchResult) -> listener.onResponse(Collections.unmodifiableList(searchResult.getSearchEntries())), 1);
|
||||
|
||||
public static void search(LDAPConnection ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<List<SearchResultEntry>> listener,
|
||||
String... attributes) {
|
||||
LdapSearchResultListener searchResultListener = new LdapSearchResultListener(
|
||||
ldap,
|
||||
ignoreReferralErrors,
|
||||
ActionListener.wrap(
|
||||
searchResult -> listener.onResponse(
|
||||
Collections.unmodifiableList(searchResult.getSearchEntries())
|
||||
),
|
||||
listener::onFailure),
|
||||
1);
|
||||
try {
|
||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
||||
false, filter, attributes);
|
||||
SearchRequest request = new SearchRequest(searchResultListener, baseDN, scope,
|
||||
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||
searchResultListener.setSearchRequest(request);
|
||||
ldap.asyncSearch(request);
|
||||
} catch (LDAPException e) {
|
||||
|
@ -177,20 +217,35 @@ public final class LdapUtils {
|
|||
/**
|
||||
* This method performs an asynchronous ldap search operation that could have multiple results
|
||||
*/
|
||||
public static void search(LDAPConnectionPool ldap, String baseDN, SearchScope scope, Filter filter, int timeLimitSeconds,
|
||||
ActionListener<List<SearchResultEntry>> listener, String... attributes) {
|
||||
public static void search(LDAPConnectionPool ldap, String baseDN, SearchScope scope,
|
||||
Filter filter, int timeLimitSeconds,
|
||||
boolean ignoreReferralErrors,
|
||||
ActionListener<List<SearchResultEntry>> listener,
|
||||
String... attributes) {
|
||||
boolean searching = false;
|
||||
LDAPConnection ldapConnection = null;
|
||||
try {
|
||||
ldapConnection = ldap.getConnection();
|
||||
final LDAPConnection finalConnection = ldapConnection;
|
||||
LdapSearchResultListener ldapSearchResultListener = new LdapSearchResultListener(ldapConnection,
|
||||
(asyncRequestID, searchResult) -> {
|
||||
IOUtils.closeWhileHandlingException(() -> ldap.releaseConnection(finalConnection));
|
||||
listener.onResponse(Collections.unmodifiableList(searchResult.getSearchEntries()));
|
||||
}, 1);
|
||||
SearchRequest request = new SearchRequest(ldapSearchResultListener, baseDN, scope, DereferencePolicy.NEVER, 0, timeLimitSeconds,
|
||||
false, filter, attributes);
|
||||
LdapSearchResultListener ldapSearchResultListener = new LdapSearchResultListener(
|
||||
ldapConnection, ignoreReferralErrors,
|
||||
ActionListener.wrap(
|
||||
searchResult -> {
|
||||
IOUtils.closeWhileHandlingException(
|
||||
() -> ldap.releaseConnection(finalConnection)
|
||||
);
|
||||
listener.onResponse(Collections.unmodifiableList(
|
||||
searchResult.getSearchEntries()
|
||||
));
|
||||
}, (e) -> {
|
||||
IOUtils.closeWhileHandlingException(
|
||||
() -> ldap.releaseConnection(finalConnection)
|
||||
);
|
||||
listener.onFailure(e);
|
||||
}),
|
||||
1);
|
||||
SearchRequest request = new SearchRequest(ldapSearchResultListener, baseDN, scope,
|
||||
DereferencePolicy.NEVER, 0, timeLimitSeconds, false, filter, attributes);
|
||||
ldapSearchResultListener.setSearchRequest(request);
|
||||
finalConnection.asyncSearch(request);
|
||||
searching = true;
|
||||
|
@ -204,9 +259,51 @@ public final class LdapUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static Filter createFilter(String filterTemplate, String... arguments) throws LDAPException {
|
||||
return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT).format((Object[]) encodeFilterValues(arguments),
|
||||
new StringBuffer(), null).toString());
|
||||
/**
|
||||
* Returns <code>true</code> if the provide {@link SearchResult} was successfully completed
|
||||
* by the server.
|
||||
* <strong>Note:</strong> Referrals are <em>not</em> considered a successful response for the
|
||||
* purposes of this method.
|
||||
*/
|
||||
private static boolean isSuccess(SearchResult searchResult) {
|
||||
switch (searchResult.getResultCode().intValue()) {
|
||||
case ResultCode.SUCCESS_INT_VALUE:
|
||||
case ResultCode.COMPARE_FALSE_INT_VALUE:
|
||||
case ResultCode.COMPARE_TRUE_INT_VALUE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static SearchResult emptyResult(SearchResult parentResult) {
|
||||
return new SearchResult(
|
||||
parentResult.getMessageID(),
|
||||
ResultCode.SUCCESS,
|
||||
"Empty result",
|
||||
parentResult.getMatchedDN(),
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private static LDAPException toException(SearchResult searchResult) {
|
||||
return new LDAPException(
|
||||
searchResult.getResultCode(),
|
||||
searchResult.getDiagnosticMessage(),
|
||||
searchResult.getMatchedDN(),
|
||||
searchResult.getReferralURLs(),
|
||||
searchResult.getResponseControls()
|
||||
);
|
||||
}
|
||||
|
||||
public static Filter createFilter(String filterTemplate, String... arguments)
|
||||
throws LDAPException {
|
||||
return Filter.create(new MessageFormat(filterTemplate, Locale.ROOT)
|
||||
.format((Object[]) encodeFilterValues(arguments), new StringBuffer(), null)
|
||||
.toString());
|
||||
}
|
||||
|
||||
public static String[] attributesToSearchFor(String[] attributes) {
|
||||
|
@ -222,35 +319,41 @@ public final class LdapUtils {
|
|||
|
||||
private static class SingleEntryListener extends LdapSearchResultListener {
|
||||
|
||||
SingleEntryListener(LDAPConnection ldapConnection, ActionListener<SearchResultEntry> listener, Filter filter) {
|
||||
super(ldapConnection, ((asyncRequestID, searchResult) -> {
|
||||
SingleEntryListener(LDAPConnection ldapConnection,
|
||||
ActionListener<SearchResultEntry> listener, Filter filter,
|
||||
boolean ignoreReferralErrors) {
|
||||
super(ldapConnection, ignoreReferralErrors, ActionListener.wrap(searchResult -> {
|
||||
final List<SearchResultEntry> entryList = searchResult.getSearchEntries();
|
||||
if (entryList.size() > 1) {
|
||||
listener.onFailure(Exceptions.authenticationError("multiple search results found for [{}]", filter));
|
||||
listener.onFailure(Exceptions.authenticationError(
|
||||
"multiple search results found for [{}]", filter));
|
||||
} else if (entryList.size() == 1) {
|
||||
listener.onResponse(entryList.get(0));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}), 1);
|
||||
}, listener::onFailure)
|
||||
, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class LdapSearchResultListener implements AsyncSearchResultListener {
|
||||
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
||||
|
||||
private final List<SearchResultEntry> entryList = new ArrayList<>();
|
||||
private final List<SearchResultReference> referenceList = new ArrayList<>();
|
||||
protected final SetOnce<SearchRequest> searchRequestRef = new SetOnce<>();
|
||||
private final BiConsumer<AsyncRequestID, SearchResult> consumer;
|
||||
|
||||
private final LDAPConnection ldapConnection;
|
||||
private final boolean ignoreReferralErrors;
|
||||
private final ActionListener<SearchResult> listener;
|
||||
private final int depth;
|
||||
|
||||
LdapSearchResultListener(LDAPConnection ldapConnection, BiConsumer<AsyncRequestID, SearchResult> consumer, int depth) {
|
||||
LdapSearchResultListener(LDAPConnection ldapConnection, boolean ignoreReferralErrors,
|
||||
ActionListener<SearchResult> listener, int depth) {
|
||||
this.ldapConnection = ldapConnection;
|
||||
this.consumer = consumer;
|
||||
this.listener = listener;
|
||||
this.depth = depth;
|
||||
this.ignoreReferralErrors = ignoreReferralErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -265,42 +368,56 @@ public final class LdapUtils {
|
|||
|
||||
@Override
|
||||
public void searchResultReceived(AsyncRequestID requestID, SearchResult searchResult) {
|
||||
// whenever we get a search result we need to check for a referral. A referral is a mechanism for an LDAP server to reference
|
||||
// an object stored in a different LDAP server/partition. There are cases where we need to follow a referral in order to get
|
||||
// the actual object we are searching for
|
||||
// Whenever we get a search result we need to check for a referral.
|
||||
// A referral is a mechanism for an LDAP server to reference an object stored in a
|
||||
// different LDAP server/partition. There are cases where we need to follow a referral
|
||||
// in order to get the actual object we are searching for
|
||||
final String[] referralUrls = referenceList.stream()
|
||||
.flatMap((ref) -> Arrays.stream(ref.getReferralURLs()))
|
||||
.collect(Collectors.toList())
|
||||
.toArray(Strings.EMPTY_ARRAY);
|
||||
final SearchRequest searchRequest = searchRequestRef.get();
|
||||
if (referralUrls.length == 0 || searchRequest.followReferrals(ldapConnection) == false) {
|
||||
// either no referrals to follow or we have explicitly disabled referral following on the connection so we just create
|
||||
// a new search result that has the values we've collected. The search result passed to this method will not have of the
|
||||
// entries as we are using a result listener and the results are not being collected by the LDAP library
|
||||
LOGGER.trace("LDAP Search {} => {} ({})", searchRequest, searchResult, entryList);
|
||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), searchResult.getResultCode(), searchResult
|
||||
.getDiagnosticMessage(), searchResult.getMatchedDN(), referralUrls, entryList, referenceList, entryList.size(),
|
||||
referenceList.size(), searchResult.getResponseControls());
|
||||
consumer.accept(requestID, resultWithValues);
|
||||
final SearchRequest request = searchRequestRef.get();
|
||||
if (referralUrls.length == 0 || request.followReferrals(ldapConnection) == false) {
|
||||
// either no referrals to follow or we have explicitly disabled referral following
|
||||
// on the connection so we just create a new search result that has the values we've
|
||||
// collected. The search result passed to this method will not have of the entries
|
||||
// as we are using a result listener and the results are not being collected by the
|
||||
// LDAP library
|
||||
LOGGER.trace("LDAP Search {} => {} ({})", request, searchResult, entryList);
|
||||
if (isSuccess(searchResult)) {
|
||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(),
|
||||
searchResult.getResultCode(), searchResult.getDiagnosticMessage(),
|
||||
searchResult.getMatchedDN(), referralUrls, entryList, referenceList,
|
||||
entryList.size(), referenceList.size(),
|
||||
searchResult.getResponseControls());
|
||||
listener.onResponse(resultWithValues);
|
||||
} else {
|
||||
listener.onFailure(toException(searchResult));
|
||||
}
|
||||
} else if (depth >= ldapConnection.getConnectionOptions().getReferralHopLimit()) {
|
||||
// we've gone through too many levels of referrals so we terminate with the values collected so far and the proper result
|
||||
// code to indicate the search was terminated early
|
||||
LOGGER.trace("Referral limit exceeded {} => {} ({})", searchRequest, searchResult, entryList);
|
||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), ResultCode.REFERRAL_LIMIT_EXCEEDED,
|
||||
searchResult.getDiagnosticMessage(), searchResult.getMatchedDN(), referralUrls, entryList, referenceList,
|
||||
entryList.size(), referenceList.size(), searchResult.getResponseControls());
|
||||
consumer.accept(requestID, resultWithValues);
|
||||
// we've gone through too many levels of referrals so we terminate with the values
|
||||
// collected so far and the proper result code to indicate the search was
|
||||
// terminated early
|
||||
LOGGER.trace("Referral limit exceeded {} => {} ({})",
|
||||
request, searchResult, entryList);
|
||||
listener.onFailure(new LDAPException(ResultCode.REFERRAL_LIMIT_EXCEEDED,
|
||||
"Referral limit exceeded (" + depth + ")",
|
||||
searchResult.getMatchedDN(), referralUrls,
|
||||
searchResult.getResponseControls()));
|
||||
} else {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("LDAP referred elsewhere {} => {}", searchRequest, Arrays.toString(referralUrls));
|
||||
LOGGER.trace("LDAP referred elsewhere {} => {}",
|
||||
request, Arrays.toString(referralUrls));
|
||||
}
|
||||
// there are referrals to follow, so we start the process to follow the referrals
|
||||
final CountDown countDown = new CountDown(referralUrls.length);
|
||||
final List<String> referralUrlsList = new ArrayList<>(Arrays.asList(referralUrls));
|
||||
|
||||
BiConsumer<AsyncRequestID, SearchResult> referralConsumer = (reqID, innerResult) -> {
|
||||
// synchronize here since we are possibly sending out a lot of requests and the result lists are not thread safe and
|
||||
// this also provides us with a consistent view
|
||||
ActionListener<SearchResult> referralListener = ActionListener.wrap(
|
||||
innerResult -> {
|
||||
// synchronize here since we are possibly sending out a lot of requests
|
||||
// and the result lists are not thread safe and this also provides us
|
||||
// with a consistent view
|
||||
synchronized (this) {
|
||||
if (innerResult.getSearchEntries() != null) {
|
||||
entryList.addAll(innerResult.getSearchEntries());
|
||||
|
@ -310,27 +427,38 @@ public final class LdapUtils {
|
|||
}
|
||||
}
|
||||
|
||||
// count down and once all referrals have been traversed then we can create the results
|
||||
// count down and once all referrals have been traversed then we can
|
||||
// create the results
|
||||
if (countDown.countDown()) {
|
||||
SearchResult resultWithValues = new SearchResult(searchResult.getMessageID(), searchResult.getResultCode(),
|
||||
searchResult.getDiagnosticMessage(), searchResult.getMatchedDN(),
|
||||
referralUrlsList.toArray(Strings.EMPTY_ARRAY), entryList, referenceList,
|
||||
entryList.size(), referenceList.size(), searchResult.getResponseControls());
|
||||
consumer.accept(requestID, resultWithValues);
|
||||
SearchResult resultWithValues = new SearchResult(
|
||||
searchResult.getMessageID(), searchResult.getResultCode(),
|
||||
searchResult.getDiagnosticMessage(),
|
||||
searchResult.getMatchedDN(),
|
||||
referralUrlsList.toArray(Strings.EMPTY_ARRAY), entryList,
|
||||
referenceList, entryList.size(), referenceList.size(),
|
||||
searchResult.getResponseControls());
|
||||
listener.onResponse(resultWithValues);
|
||||
}
|
||||
};
|
||||
}, listener::onFailure);
|
||||
|
||||
for (String referralUrl : referralUrls) {
|
||||
try {
|
||||
// for each referral follow it and any other referrals returned until we get to a depth that is greater than or
|
||||
// equal to the referral hop limit or all referrals have been followed. Each time referrals are followed from a
|
||||
// search result, the depth increases by 1
|
||||
followReferral(ldapConnection, referralUrl, searchRequest, referralConsumer, depth + 1, searchResult, requestID);
|
||||
// for each referral follow it and any other referrals returned until we
|
||||
// get to a depth that is greater than or equal to the referral hop limit
|
||||
// or all referrals have been followed. Each time referrals are followed
|
||||
// from a search result, the depth increases by 1
|
||||
followReferral(ldapConnection, referralUrl, request, referralListener,
|
||||
depth + 1, ignoreReferralErrors, searchResult);
|
||||
} catch (LDAPException e) {
|
||||
LOGGER.warn((Supplier<?>)
|
||||
() -> new ParameterizedMessage("caught exception while trying to follow referral [{}]", referralUrl), e);
|
||||
referralConsumer.accept(requestID, new SearchResult(searchResult.getMessageID(), e.getResultCode(),
|
||||
e.getDiagnosticMessage(), e.getMatchedDN(), e.getReferralURLs(), 0, 0, e.getResponseControls()));
|
||||
LOGGER.warn((Supplier<?>) () -> new ParameterizedMessage(
|
||||
"caught exception while trying to follow referral [{}]",
|
||||
referralUrl), e);
|
||||
if (ignoreReferralErrors) {
|
||||
// Needed in order for the countDown to be correct
|
||||
referralListener.onResponse(emptyResult(searchResult));
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,19 +470,27 @@ public final class LdapUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Performs the actual connection and following of a referral given a URL string. This referral is being followed as it may contain a
|
||||
* result that is relevant to our search
|
||||
* Performs the actual connection and following of a referral given a URL string.
|
||||
* This referral is being followed as it may contain a result that is relevant to our search
|
||||
*/
|
||||
private static void followReferral(LDAPConnection ldapConnection, String urlString, SearchRequest searchRequest,
|
||||
BiConsumer<AsyncRequestID, SearchResult> consumer, int depth,
|
||||
SearchResult originatingResult, AsyncRequestID asyncRequestID) throws LDAPException {
|
||||
private static void followReferral(LDAPConnection ldapConnection, String urlString,
|
||||
SearchRequest searchRequest,
|
||||
ActionListener<SearchResult> listener, int depth,
|
||||
boolean ignoreErrors, SearchResult originatingResult)
|
||||
throws LDAPException {
|
||||
|
||||
final LDAPURL referralURL = new LDAPURL(urlString);
|
||||
final String host = referralURL.getHost();
|
||||
|
||||
// the host must be present in order to follow a referral
|
||||
if (host != null) {
|
||||
// the referral URL often contains information necessary about the LDAP request such as the base DN, scope, and filter. If it
|
||||
// does not, then we reuse the values from the originating search request
|
||||
if (host == null) {
|
||||
// nothing to really do since a null host cannot really be handled, so we treat it as
|
||||
// an error
|
||||
throw new LDAPException(ResultCode.UNAVAILABLE, "Null referral host in " + urlString);
|
||||
}
|
||||
|
||||
// the referral URL often contains information necessary about the LDAP request such as
|
||||
// the base DN, scope, and filter. If it does not, then we reuse the values from the
|
||||
// originating search request
|
||||
final String requestBaseDN;
|
||||
if (referralURL.baseDNProvided()) {
|
||||
requestBaseDN = referralURL.getBaseDN().toString();
|
||||
|
@ -376,23 +512,40 @@ public final class LdapUtils {
|
|||
requestFilter = searchRequest.getFilter();
|
||||
}
|
||||
|
||||
// in order to follow the referral we need to open a new connection and we do so using the referral connector on the ldap
|
||||
// connection
|
||||
final LDAPConnection referralConn =
|
||||
ldapConnection.getReferralConnector().getReferralConnection(referralURL, ldapConnection);
|
||||
final LdapSearchResultListener listener = new LdapSearchResultListener(referralConn,
|
||||
(reqId, searchResult) -> {
|
||||
// in order to follow the referral we need to open a new connection and we do so using the
|
||||
// referral connector on the ldap connection
|
||||
final LDAPConnection referralConn = ldapConnection.getReferralConnector()
|
||||
.getReferralConnection(referralURL, ldapConnection);
|
||||
final LdapSearchResultListener ldapListener = new LdapSearchResultListener(
|
||||
referralConn, ignoreErrors,
|
||||
ActionListener.wrap(
|
||||
searchResult -> {
|
||||
IOUtils.closeWhileHandlingException(referralConn);
|
||||
consumer.accept(reqId, searchResult);
|
||||
}, depth);
|
||||
listener.onResponse(searchResult);
|
||||
},
|
||||
e -> {
|
||||
if (ignoreErrors) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(new ParameterizedMessage(
|
||||
"Failed to retrieve results from referral URL [{}]." +
|
||||
" Treating as 'no results'",
|
||||
referralURL), e);
|
||||
}
|
||||
listener.onResponse(emptyResult(originatingResult));
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}),
|
||||
depth);
|
||||
boolean success = false;
|
||||
try {
|
||||
final SearchRequest referralSearchRequest =
|
||||
new SearchRequest(listener, searchRequest.getControls(),
|
||||
new SearchRequest(ldapListener, searchRequest.getControls(),
|
||||
requestBaseDN, requestScope, searchRequest.getDereferencePolicy(),
|
||||
searchRequest.getSizeLimit(), searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
|
||||
requestFilter, searchRequest.getAttributes());
|
||||
listener.setSearchRequest(searchRequest);
|
||||
searchRequest.getSizeLimit(), searchRequest.getTimeLimitSeconds(),
|
||||
searchRequest.typesOnly(), requestFilter,
|
||||
searchRequest.getAttributes());
|
||||
ldapListener.setSearchRequest(searchRequest);
|
||||
referralConn.asyncSearch(referralSearchRequest);
|
||||
success = true;
|
||||
} finally {
|
||||
|
@ -400,10 +553,5 @@ public final class LdapUtils {
|
|||
IOUtils.closeWhileHandlingException(referralConn);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// nothing to really do since a null host cannot really be handled, so we just return with a response that is empty...
|
||||
consumer.accept(asyncRequestID, new SearchResult(originatingResult.getMessageID(), ResultCode.UNAVAILABLE,
|
||||
null, null, null, Collections.emptyList(), Collections.emptyList(), 0, 0, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ import java.util.regex.Pattern;
|
|||
|
||||
/**
|
||||
* This factory holds settings needed for authenticating to LDAP and creating LdapConnections.
|
||||
* Each created LdapConnection needs to be closed or else connections will pill up consuming resources.
|
||||
* Each created LdapConnection needs to be closed or else connections will pill up consuming
|
||||
* resources.
|
||||
* <p>
|
||||
* A standard looking usage pattern could look like this:
|
||||
* <pre>
|
||||
|
@ -52,9 +53,14 @@ public abstract class SessionFactory {
|
|||
public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search";
|
||||
public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification";
|
||||
public static final String FOLLOW_REFERRALS_SETTING = "follow_referrals";
|
||||
public static final Setting<Boolean> IGNORE_REFERRAL_ERRORS_SETTING = Setting.boolSetting(
|
||||
"ignore_referral_errors", true, Setting.Property.NodeScope);
|
||||
|
||||
public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5);
|
||||
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
protected final Logger logger;
|
||||
protected final RealmConfig config;
|
||||
|
@ -63,36 +69,43 @@ public abstract class SessionFactory {
|
|||
|
||||
protected final ServerSet serverSet;
|
||||
protected final boolean sslUsed;
|
||||
protected final boolean ignoreReferralErrors;
|
||||
|
||||
protected SessionFactory(RealmConfig config, SSLService sslService) {
|
||||
this.config = config;
|
||||
this.logger = config.logger(getClass());
|
||||
TimeValue searchTimeout = config.settings().getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT);
|
||||
final Settings settings = config.settings();
|
||||
TimeValue searchTimeout = settings.getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT);
|
||||
if (searchTimeout.millis() < 1000L) {
|
||||
logger.warn("ldap_search timeout [{}] is less than the minimum supported search timeout of 1s. using 1s",
|
||||
logger.warn("ldap_search timeout [{}] is less than the minimum supported search " +
|
||||
"timeout of 1s. using 1s",
|
||||
searchTimeout.millis());
|
||||
searchTimeout = TimeValue.timeValueSeconds(1L);
|
||||
}
|
||||
this.timeout = searchTimeout;
|
||||
this.sslService = sslService;
|
||||
LDAPServers ldapServers = ldapServers(config.settings());
|
||||
LDAPServers ldapServers = ldapServers(settings);
|
||||
this.serverSet = serverSet(config, sslService, ldapServers);
|
||||
this.sslUsed = ldapServers.ssl;
|
||||
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the given user and opens a new connection that bound to it (meaning, all operations
|
||||
* under the returned connection will be executed on behalf of the authenticated user.
|
||||
* Authenticates the given user and opens a new connection that bound to it (meaning, all
|
||||
* operations under the returned connection will be executed on behalf of the authenticated
|
||||
* user.
|
||||
*
|
||||
* @param user The name of the user to authenticate the connection with.
|
||||
* @param password The password of the user
|
||||
* @param listener the listener to call on a failure or result
|
||||
*/
|
||||
public abstract void session(String user, SecuredString password, ActionListener<LdapSession> listener);
|
||||
public abstract void session(String user, SecuredString password,
|
||||
ActionListener<LdapSession> listener);
|
||||
|
||||
/**
|
||||
* Returns a flag to indicate if this session factory supports unauthenticated sessions. This means that a session can
|
||||
* be established without providing any credentials in a call to {@link #unauthenticatedSession(String, ActionListener)}
|
||||
* Returns a flag to indicate if this session factory supports unauthenticated sessions.
|
||||
* This means that a session can be established without providing any credentials in a call to
|
||||
* {@link #unauthenticatedSession(String, ActionListener)}
|
||||
*
|
||||
* @return true if the factory supports unauthenticated sessions
|
||||
*/
|
||||
|
@ -110,29 +123,43 @@ public abstract class SessionFactory {
|
|||
throw new UnsupportedOperationException("unauthenticated sessions are not supported");
|
||||
}
|
||||
|
||||
protected static LDAPConnectionOptions connectionOptions(RealmConfig config, SSLService sslService, Logger logger) {
|
||||
protected static LDAPConnectionOptions connectionOptions(RealmConfig config,
|
||||
SSLService sslService, Logger logger) {
|
||||
Settings realmSettings = config.settings();
|
||||
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||
options.setConnectTimeoutMillis(Math.toIntExact(realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()));
|
||||
options.setConnectTimeoutMillis(Math.toIntExact(
|
||||
realmSettings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()
|
||||
));
|
||||
options.setFollowReferrals(realmSettings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true));
|
||||
options.setResponseTimeoutMillis(realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis());
|
||||
options.setResponseTimeoutMillis(
|
||||
realmSettings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()
|
||||
);
|
||||
options.setAllowConcurrentSocketFactoryUse(true);
|
||||
SSLConfigurationSettings sslConfigurationSettings = SSLConfigurationSettings.withoutPrefix();
|
||||
|
||||
final SSLConfigurationSettings sslConfigurationSettings =
|
||||
SSLConfigurationSettings.withoutPrefix();
|
||||
final Settings realmSSLSettings = realmSettings.getByPrefix("ssl.");
|
||||
final boolean verificationModeExists = sslConfigurationSettings.verificationMode.exists(realmSSLSettings);
|
||||
final boolean hostnameVerficationExists = realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null;
|
||||
final boolean verificationModeExists =
|
||||
sslConfigurationSettings.verificationMode.exists(realmSSLSettings);
|
||||
final boolean hostnameVerficationExists =
|
||||
realmSettings.get(HOSTNAME_VERIFICATION_SETTING, null) != null;
|
||||
|
||||
if (verificationModeExists && hostnameVerficationExists) {
|
||||
throw new IllegalArgumentException("[" + HOSTNAME_VERIFICATION_SETTING + "] and [" +
|
||||
sslConfigurationSettings.verificationMode.getKey() + "] may not be used at the same time");
|
||||
sslConfigurationSettings.verificationMode.getKey() +
|
||||
"] may not be used at the same time");
|
||||
} else if (verificationModeExists) {
|
||||
VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings, Settings.EMPTY);
|
||||
VerificationMode verificationMode = sslService.getVerificationMode(realmSSLSettings,
|
||||
Settings.EMPTY);
|
||||
if (verificationMode == VerificationMode.FULL) {
|
||||
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
||||
}
|
||||
} else if (hostnameVerficationExists) {
|
||||
new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and will be removed in a future version. use " +
|
||||
"[{}] instead", RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING),
|
||||
RealmSettings.getFullSettingKey(config, "ssl." + sslConfigurationSettings.verificationMode.getKey()));
|
||||
new DeprecationLogger(logger).deprecated("the setting [{}] has been deprecated and " +
|
||||
"will be removed in a future version. use [{}] instead",
|
||||
RealmSettings.getFullSettingKey(config, HOSTNAME_VERIFICATION_SETTING),
|
||||
RealmSettings.getFullSettingKey(config, "ssl." +
|
||||
sslConfigurationSettings.verificationMode.getKey()));
|
||||
if (realmSettings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
|
||||
options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true));
|
||||
}
|
||||
|
@ -146,7 +173,8 @@ public abstract class SessionFactory {
|
|||
// Parse LDAP urls
|
||||
String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings));
|
||||
if (ldapUrls == null || ldapUrls.length == 0) {
|
||||
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]");
|
||||
throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING +
|
||||
"]");
|
||||
}
|
||||
return new LDAPServers(ldapUrls);
|
||||
}
|
||||
|
@ -155,7 +183,8 @@ public abstract class SessionFactory {
|
|||
return null;
|
||||
}
|
||||
|
||||
private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService, LDAPServers ldapServers) {
|
||||
private ServerSet serverSet(RealmConfig realmConfig, SSLService clientSSLService,
|
||||
LDAPServers ldapServers) {
|
||||
Settings settings = realmConfig.settings();
|
||||
SocketFactory socketFactory = null;
|
||||
if (ldapServers.ssl()) {
|
||||
|
@ -166,8 +195,8 @@ public abstract class SessionFactory {
|
|||
logger.debug("using encryption for LDAP connections without hostname verification");
|
||||
}
|
||||
}
|
||||
return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings, socketFactory,
|
||||
connectionOptions(realmConfig, sslService, logger));
|
||||
return LdapLoadBalancing.serverSet(ldapServers.addresses(), ldapServers.ports(), settings,
|
||||
socketFactory, connectionOptions(realmConfig, sslService, logger));
|
||||
}
|
||||
|
||||
// package private to use for testing
|
||||
|
@ -182,12 +211,19 @@ public abstract class SessionFactory {
|
|||
protected static Set<Setting<?>> getSettings() {
|
||||
Set<Setting<?>> settings = new HashSet<>();
|
||||
settings.addAll(LdapLoadBalancing.getSettings());
|
||||
settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope));
|
||||
settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true, Setting.Property.NodeScope));
|
||||
settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true, Setting.Property.NodeScope));
|
||||
settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(),
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT,
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT,
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT,
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true,
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true,
|
||||
Setting.Property.NodeScope));
|
||||
settings.add(IGNORE_REFERRAL_ERRORS_SETTING);
|
||||
settings.addAll(SSLConfigurationSettings.withPrefix("ssl.").getAllSettings());
|
||||
return settings;
|
||||
}
|
||||
|
@ -208,7 +244,8 @@ public abstract class SessionFactory {
|
|||
addresses[i] = url.getHost();
|
||||
ports[i] = url.getPort();
|
||||
} catch (LDAPException e) {
|
||||
throw new IllegalArgumentException("unable to parse configured LDAP url [" + urls[i] + "]", e);
|
||||
throw new IllegalArgumentException("unable to parse configured LDAP url [" +
|
||||
urls[i] + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,13 +270,16 @@ public abstract class SessionFactory {
|
|||
return true;
|
||||
}
|
||||
|
||||
final boolean allSecure = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find());
|
||||
final boolean allClear = Arrays.stream(ldapUrls).allMatch(s -> STARTS_WITH_LDAP.matcher(s).find());
|
||||
final boolean allSecure = Arrays.stream(ldapUrls)
|
||||
.allMatch(s -> STARTS_WITH_LDAPS.matcher(s).find());
|
||||
final boolean allClear = Arrays.stream(ldapUrls)
|
||||
.allMatch(s -> STARTS_WITH_LDAP.matcher(s).find());
|
||||
|
||||
if (!allSecure && !allClear) {
|
||||
//No mixing is allowed because we use the same socketfactory
|
||||
throw new IllegalArgumentException("configured LDAP protocols are not all equal (ldaps://.. and ldap://..): [" +
|
||||
Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
|
||||
throw new IllegalArgumentException(
|
||||
"configured LDAP protocols are not all equal (ldaps://.. and ldap://..): ["
|
||||
+ Strings.arrayToCommaDelimitedString(ldapUrls) + "]");
|
||||
}
|
||||
|
||||
return allSecure;
|
||||
|
|
|
@ -24,15 +24,17 @@ import static org.hamcrest.Matchers.is;
|
|||
@Network
|
||||
public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
||||
|
||||
private static final String BRUCE_BANNER_DN = "cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
private static final String BRUCE_BANNER_DN =
|
||||
"cn=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
|
||||
public void testResolveSubTree() throws Exception {
|
||||
Settings settings = Settings.builder()
|
||||
.put("scope", LdapSearchScope.SUB_TREE)
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
||||
NoOpLogger.INSTANCE, null);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
|
@ -48,9 +50,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
.put("scope", LdapSearchScope.ONE_LEVEL)
|
||||
.put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com")
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
||||
NoOpLogger.INSTANCE, null);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, hasItem(containsString("Users")));
|
||||
}
|
||||
|
||||
|
@ -59,9 +62,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
.put("scope", LdapSearchScope.BASE)
|
||||
.put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com");
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10),
|
||||
NoOpLogger.INSTANCE, null);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin")));
|
||||
}
|
||||
|
||||
|
@ -74,7 +78,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
};
|
||||
final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||
TimeValue.timeValueSeconds(10), false, future);
|
||||
Filter query = future.actionGet();
|
||||
assertValidSidQuery(query, expectedSids);
|
||||
}
|
||||
|
@ -87,7 +92,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
"S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group
|
||||
final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com";
|
||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||
TimeValue.timeValueSeconds(10), false, future);
|
||||
Filter query = future.actionGet();
|
||||
assertValidSidQuery(query, expectedSids);
|
||||
}
|
||||
|
@ -105,7 +111,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
|
||||
final String dn = BRUCE_BANNER_DN;
|
||||
PlainActionFuture<Filter> future = new PlainActionFuture<>();
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), future);
|
||||
ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn,
|
||||
TimeValue.timeValueSeconds(10), false, future);
|
||||
Filter query = future.actionGet();
|
||||
assertValidSidQuery(query, expectedSids);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.xpack.security.authc.ldap;
|
||||
|
||||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import com.unboundid.ldap.sdk.ResultCode;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
|
||||
|
@ -32,6 +33,8 @@ import static org.hamcrest.Matchers.is;
|
|||
@Network
|
||||
public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryIntegTests {
|
||||
|
||||
private final SecuredString SECURED_PASSWORD = SecuredStringTests.build(PASSWORD);
|
||||
|
||||
@Override
|
||||
public boolean enableWarningsCheck() {
|
||||
return false;
|
||||
|
@ -39,11 +42,14 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAdAuth() throws Exception {
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings);
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config = new RealmConfig("ad-test",
|
||||
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false),
|
||||
globalSettings);
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config,
|
||||
sslService);
|
||||
|
||||
String userName = "ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
|
@ -64,7 +70,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
String userName = "ades\\ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
|
@ -91,7 +97,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
||||
session(sessionFactory, "ironman", SecuredStringTests.build(PASSWORD)).groups(groups);
|
||||
session(sessionFactory, "ironman", SECURED_PASSWORD).groups(groups);
|
||||
LDAPException expected = expectThrows(LDAPException.class, groups::actionGet);
|
||||
assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting"));
|
||||
}
|
||||
|
@ -102,7 +108,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
|
||||
for(String user: users) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
assertThat("group avenger test for user "+user, groups(ldap), hasItem(containsString("Avengers")));
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +122,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -138,7 +144,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -164,7 +170,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, hasItem(containsString("Avengers")));
|
||||
|
@ -180,7 +186,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
//Login with the UserPrincipalName
|
||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -198,7 +204,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
//login with sAMAccountName
|
||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
try (LdapSession ldap = session(sessionFactory, "selvig", SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, "selvig", SECURED_PASSWORD)) {
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
|
||||
List<String> groups = groups(ldap);
|
||||
|
@ -221,7 +227,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
|
||||
//Login with the UserPrincipalName
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("CN=Geniuses"),
|
||||
|
@ -235,7 +241,13 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
public void testStandardLdapConnection() throws Exception {
|
||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
Settings settings = LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE);
|
||||
Settings settings = LdapTestCase.buildLdapSettings(
|
||||
new String[] { AD_LDAP_URL },
|
||||
new String[] { userTemplate },
|
||||
groupSearchBase,
|
||||
LdapSearchScope.SUB_TREE,
|
||||
null,
|
||||
true);
|
||||
if (useGlobalSSL == false) {
|
||||
settings = Settings.builder()
|
||||
.put(settings)
|
||||
|
@ -247,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -258,6 +270,42 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testHandlingLdapReferralErrors() throws Exception {
|
||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
final boolean ignoreReferralErrors = false;
|
||||
Settings settings = LdapTestCase.buildLdapSettings(
|
||||
new String[] { AD_LDAP_URL },
|
||||
new String[] { userTemplate },
|
||||
groupSearchBase,
|
||||
LdapSearchScope.SUB_TREE,
|
||||
null,
|
||||
ignoreReferralErrors);
|
||||
if (useGlobalSSL == false) {
|
||||
settings = Settings.builder()
|
||||
.put(settings)
|
||||
.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
|
||||
.put("ssl.truststore.password", "changeit")
|
||||
.build();
|
||||
}
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings);
|
||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
final UncategorizedExecutionException exception = expectThrows(
|
||||
UncategorizedExecutionException.class,
|
||||
() -> groups(ldap)
|
||||
);
|
||||
final Throwable cause = exception.getCause();
|
||||
assertThat(cause, instanceOf(ExecutionException.class));
|
||||
assertThat(cause.getCause(), instanceOf(LDAPException.class));
|
||||
final LDAPException ldapException = (LDAPException) cause.getCause();
|
||||
assertThat(ldapException.getResultCode(), is(ResultCode.INVALID_CREDENTIALS));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testStandardLdapWithAttributeGroups() throws Exception {
|
||||
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
|
@ -273,7 +321,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
try (LdapSession ldap = session(sessionFactory, user, SecuredStringTests.build(PASSWORD))) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -290,7 +338,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
String userName = "ironman";
|
||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||
() -> session(sessionFactory, userName, SecuredStringTests.build(PASSWORD)));
|
||||
() -> session(sessionFactory, userName, SECURED_PASSWORD));
|
||||
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
||||
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
||||
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
||||
|
@ -309,7 +357,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
|
||||
String user = "Bruce Banner";
|
||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||
() -> session(sessionFactory, user, SecuredStringTests.build(PASSWORD)));
|
||||
() -> session(sessionFactory, user, SECURED_PASSWORD));
|
||||
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
||||
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
||||
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.elasticsearch.xpack.security.authc.RealmConfig;
|
|||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||
import org.elasticsearch.xpack.security.authc.support.SecuredStringTests;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
|
@ -307,8 +308,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
Settings settings = Settings.builder()
|
||||
.put(LdapTestCase.buildLdapSettings(new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL }, Strings.EMPTY_ARRAY,
|
||||
groupSearchBase, LdapSearchScope.SUB_TREE))
|
||||
.put(LdapTestCase.buildLdapSettings(
|
||||
new String[] { ActiveDirectorySessionFactoryTests.AD_LDAP_URL },
|
||||
Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE, null,
|
||||
true))
|
||||
.put("user_search.base_dn", userSearchBase)
|
||||
.put("bind_dn", "ironman@ad.test.elasticsearch.com")
|
||||
.put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.ldap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
|
||||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import com.unboundid.ldap.sdk.LDAPURL;
|
||||
import com.unboundid.ldap.sdk.ResultCode;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class SearchGroupsResolverInMemoryTests extends LdapTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a client-side timeout in the asynchronous LDAP SDK is treated as a failure, rather
|
||||
* than simply returning no results.
|
||||
*/
|
||||
public void testSearchTimeoutIsFailure() throws Exception {
|
||||
|
||||
ldapServers[0].setProcessingDelayMillis(100);
|
||||
|
||||
final LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||
options.setConnectTimeoutMillis(500);
|
||||
options.setResponseTimeoutMillis(5);
|
||||
|
||||
final LDAPURL ldapurl = new LDAPURL(ldapUrls()[0]);
|
||||
final LDAPConnection connection = LdapUtils.privilegedConnect(
|
||||
() -> new LDAPConnection(options,
|
||||
ldapurl.getHost(), ldapurl.getPort())
|
||||
);
|
||||
|
||||
final Settings settings = Settings.builder()
|
||||
.put("group_search.base_dn", "ou=groups,o=sevenSeas")
|
||||
.put("group_search.scope", LdapSearchScope.SUB_TREE)
|
||||
.build();
|
||||
final SearchGroupsResolver resolver = new SearchGroupsResolver(settings);
|
||||
final PlainActionFuture<List<String>> future = new PlainActionFuture<>();
|
||||
resolver.resolve(connection,
|
||||
"cn=William Bush,ou=people,o=sevenSeas",
|
||||
TimeValue.timeValueSeconds(30),
|
||||
logger,
|
||||
null, future);
|
||||
|
||||
final ExecutionException exception = expectThrows(ExecutionException.class, future::get);
|
||||
final Throwable cause = exception.getCause();
|
||||
assertThat(cause, instanceOf(LDAPException.class));
|
||||
assertThat(((LDAPException) cause).getResultCode(), is(ResultCode.TIMEOUT));
|
||||
}
|
||||
|
||||
}
|
|
@ -25,7 +25,6 @@ import org.junit.Before;
|
|||
import org.junit.BeforeClass;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -92,17 +91,27 @@ public abstract class LdapTestCase extends ESTestCase {
|
|||
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope, null);
|
||||
}
|
||||
|
||||
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, LdapSearchScope scope,
|
||||
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate,
|
||||
String groupSearchBase, LdapSearchScope scope,
|
||||
LdapLoadBalancing serverSetType) {
|
||||
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, scope,
|
||||
serverSetType, false);
|
||||
}
|
||||
|
||||
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate,
|
||||
String groupSearchBase, LdapSearchScope scope,
|
||||
LdapLoadBalancing serverSetType,
|
||||
boolean ignoreReferralErrors) {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.putArray(URLS_SETTING, ldapUrl)
|
||||
.putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate)
|
||||
.put(SessionFactory.IGNORE_REFERRAL_ERRORS_SETTING.getKey(), ignoreReferralErrors)
|
||||
.put("group_search.base_dn", groupSearchBase)
|
||||
.put("group_search.scope", scope)
|
||||
.put("ssl.verification_mode", VerificationMode.CERTIFICATE);
|
||||
if (serverSetType != null) {
|
||||
builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." + LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING,
|
||||
serverSetType.toString());
|
||||
builder.put(LdapLoadBalancing.LOAD_BALANCE_SETTINGS + "." +
|
||||
LdapLoadBalancing.LOAD_BALANCE_TYPE_SETTING, serverSetType.toString());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue