Add active directory bind user and user lookup support (elastic/x-pack-elasticsearch#1956)
This commit adds support for a bind user when using the active directory realm. The addition of a bind user also enables support for the user lookup mechanism, which is necessary to support the run as functionality that we provide. relates elastic/x-pack-elasticsearch#179 Original commit: elastic/x-pack-elasticsearch@40b07b3422
This commit is contained in:
parent
ef25568b2a
commit
e686d8a3bf
|
@ -33,6 +33,12 @@ NOTE: When you use Active Directory for authentication, the username entered by
|
|||
the user is expected to match the `sAMAccountName` or `userPrincipalName`,
|
||||
not the common name.
|
||||
|
||||
The Active Directory realm authenticates users using an LDAP bind request. After
|
||||
authenticating the user, the realm then searches to find the user's entry in
|
||||
Active Directory. Once the user has been found, the Active Directory realm then
|
||||
retrieves the user's group memberships from the `tokenGroups` attribute on the
|
||||
user's entry in Active Directory.
|
||||
|
||||
To configure an `active_directory` realm:
|
||||
|
||||
. Add a realm configuration of type `active_directory` to `elasticsearch.yml`
|
||||
|
@ -63,13 +69,10 @@ xpack:
|
|||
order: 0 <1>
|
||||
domain_name: ad.example.com
|
||||
url: ldaps://ad.example.com:636 <2>
|
||||
unmapped_groups_as_roles: true <3>
|
||||
------------------------------------------------------------
|
||||
<1> The realm order controls the order in which the configured realms are checked
|
||||
when authenticating a user.
|
||||
<2> If you don't specify the URL, it defaults to `ldap:<domain_name>:389`.
|
||||
<3> When this option is enabled, Active Directory groups are automatically mapped
|
||||
to roles of the same name.
|
||||
+
|
||||
IMPORTANT: When you configure realms in `elasticsearch.yml`, only the
|
||||
realms you specify are used for authentication. If you also want to use the
|
||||
|
@ -77,6 +80,42 @@ realms you specify are used for authentication. If you also want to use the
|
|||
|
||||
. Restart Elasticsearch.
|
||||
|
||||
===== Configuring a Bind User
|
||||
By default, all of the LDAP operations are run by the user that {security} is
|
||||
authenticating. In some cases, regular users may not be able to access all of the
|
||||
necessary items within Active Directory and a _bind user_ is needed. A bind user
|
||||
can be configured and will be used to perform all operations other than the LDAP
|
||||
bind request, which is required to authenticate the credentials provided by the user.
|
||||
|
||||
The use of a bind user enables the <<run-as-privilege,run as feature>> to be
|
||||
used with the Active Directory realm and the ability to maintain a set of pooled
|
||||
connections to Active Directory. These pooled connection reduce the number of
|
||||
resources that must be created and destroyed with every user authentication.
|
||||
|
||||
The following example shows the configuration of a bind user through the user of the
|
||||
`bind_dn` and `bind_password` settings.
|
||||
|
||||
[source, yaml]
|
||||
------------------------------------------------------------
|
||||
xpack:
|
||||
security:
|
||||
authc:
|
||||
realms:
|
||||
active_directory:
|
||||
type: active_directory
|
||||
order: 0
|
||||
domain_name: ad.example.com
|
||||
url: ldaps://ad.example.com:636
|
||||
bind_dn: es_svc_user@ad.example.com <1>
|
||||
bind_password: es_svc_user_password
|
||||
------------------------------------------------------------
|
||||
<1> This is the user that all Active Directory search requests are executed as.
|
||||
Without a bind user configured, all requests run as the user that is authenticating
|
||||
with Elasticsearch.
|
||||
|
||||
When a bind user is configured, connection pooling is enabled by default.
|
||||
Connection pooling can be disabled using the `user_search.pool.enabled` setting.
|
||||
|
||||
===== Multiple Domain Support
|
||||
When authenticating users across multiple domains in a forest, there are a few minor
|
||||
differences in the configuration and the way that users will authenticate. The `domain_name`
|
||||
|
@ -176,6 +215,14 @@ operation are supported: failover and load balancing
|
|||
assuming an unencrypted connection to port 389. For example,
|
||||
`ldap://<domain_name>:389`. This settings is required when
|
||||
connecting using SSL/TLS or via a custom port.
|
||||
| `bind_dn` | no | The DN of the user that is used to bind to Active Directory
|
||||
and perform searches. Due to its potential security
|
||||
impact, `bind_dn` is not exposed via the
|
||||
{ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API].
|
||||
| `bind_password` | no | The password for the user that is used to bind to
|
||||
Active Directory. Due to its potential security impact,
|
||||
`bind_password` is not exposed via the
|
||||
{ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API].
|
||||
| `load_balance.type` | no | The behavior to use when there are multiple LDAP URLs defined.
|
||||
For supported values see <<ad-load-balancing>>.
|
||||
| `load_balance.cache_ttl` | no | When using `dns_failover` or `dns_round_robin` as the load
|
||||
|
@ -209,6 +256,22 @@ operation are supported: failover and load balancing
|
|||
must be a valid LDAP user search filter, for example
|
||||
`(&(objectClass=user)(sAMAccountName={0}))`. For more
|
||||
information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax].
|
||||
| `user_search.pool.enabled` | no | Enables or disables connection pooling for user search. When
|
||||
disabled a new connection is created for every search. The
|
||||
default is `true` when `bind_dn` is provided.
|
||||
| `user_search.pool.size` | no | Specifies the maximum number of connections to Active Directory
|
||||
server to allow in the connection pool. Defaults to `20`.
|
||||
| `user_search.pool.initial_size` | no | The initial number of connections to create to Active Directory
|
||||
server on startup. Defaults to `0`. Values greater than `0`
|
||||
could cause startup failures if the LDAP server is down.
|
||||
| `user_search.pool.health_check.enabled` | no | Enables or disables a health check on Active Directory connections in
|
||||
the connection pool. Connections are checked in the
|
||||
background at the specified interval. Defaults to `true`.
|
||||
| `user_search.pool.health_check.dn` | no | Specifies the distinguished name to retrieve as part of
|
||||
the health check. Defaults to the value of `bind_dn` if present, and if
|
||||
not falls back to `user_search.base_dn`.
|
||||
| `user_search.pool.health_check.interval` | no | How often to perform background checks of connections in
|
||||
the pool. Defaults to `60s`.
|
||||
| `group_search.base_dn` | no | Specifies the context to search for groups in which the user
|
||||
has membership. Defaults to the root of the Active Directory
|
||||
domain.
|
||||
|
|
|
@ -8,10 +8,10 @@ users, you can use the _run as_ mechanism to restrict data access according to
|
|||
|
||||
To "run as" (impersonate) another user, you must be able to retrieve the user from
|
||||
the realm you use to authenticate. Both the internal `native` and `file` realms
|
||||
support this out of the box. The LDAP realm however must be configured to run in
|
||||
_user search_ mode. For more information, see
|
||||
<<ldap-user-search, Configuring an LDAP Realm with User Search>>.
|
||||
The Active Directory and PKI realms do not support "run as".
|
||||
support this out of the box. The LDAP realm must be configured to run in
|
||||
<<ldap-user-search, _user search_ mode>>. The Active Directory realm must be
|
||||
<<ad-settings,configured with a `bind_dn` and `bind_password`>> to support _run as_.
|
||||
The PKI realm does not support _run as_.
|
||||
|
||||
To submit requests on behalf of other users, you need to have the `run_as`
|
||||
permission. For example, the following role grants permission to submit request
|
||||
|
|
|
@ -199,7 +199,7 @@ The attribute to match with the username presented to. Defaults to `uid`.
|
|||
`user_search.pool.enabled`::
|
||||
Enables or disables connection pooling for user search. When
|
||||
disabled a new connection is created for every search. The
|
||||
default is `true`.
|
||||
default is `true` when `bind_dn` is provided.
|
||||
|
||||
`user_search.pool.size`::
|
||||
The maximum number of connections to the LDAP server to allow in the
|
||||
|
@ -207,7 +207,7 @@ connection pool. Defaults to `20`.
|
|||
|
||||
`user_search.pool.initial_size`::
|
||||
The initial number of connections to create to the LDAP server on startup.
|
||||
Defaults to `5`.
|
||||
Defaults to `0`.
|
||||
|
||||
`user_search.pool.health_check.enabled`::
|
||||
Flag to enable or disable a health check on LDAP connections in the connection
|
||||
|
@ -216,12 +216,13 @@ Defaults to `true`.
|
|||
|
||||
`user_search.pool.health_check.dn`::
|
||||
The distinguished name to be retrieved as part of the health check.
|
||||
Defaults to the value of `bind_dn`. Required if `bind_dn` is not
|
||||
specified.
|
||||
Defaults to the value of `bind_dn` if present, and if
|
||||
not falls back to `user_search.base_dn`.
|
||||
|
||||
`user_search.pool.health_check.interval`::
|
||||
The interval to perform background checks of connections in the pool.
|
||||
Defaults to `60s`.
|
||||
|
||||
`group_search.base_dn`::
|
||||
The container DN to search for groups in which the user has membership. When
|
||||
this element is absent, Security searches for the attribute specified by
|
||||
|
@ -359,6 +360,14 @@ The domain name of Active Directory. The cluster can derive the URL and
|
|||
`user_search_dn` fields from values in this element if those fields are not
|
||||
otherwise specified. Required.
|
||||
|
||||
`bind_dn`::
|
||||
The DN of the user that will be used to bind to Active Directory and perform searches.
|
||||
Defaults to Empty.
|
||||
|
||||
`bind_password`::
|
||||
The password for the user that will be used to bind to Active Directory.
|
||||
Defaults to Empty.
|
||||
|
||||
`unmapped_groups_as_roles`::
|
||||
Takes a boolean variable. When this element is set to `true`, the names of any
|
||||
LDAP groups that are not referenced in a role-mapping _file_ are used as role
|
||||
|
@ -401,6 +410,32 @@ Specifies a filter to use to lookup a user given a down level logon name
|
|||
must be a valid LDAP user search filter, for example
|
||||
`(&(objectClass=user)(sAMAccountName={0}))`.
|
||||
|
||||
`user_search.pool.enabled`::
|
||||
Enables or disables connection pooling for user search. When
|
||||
disabled a new connection is created for every search. The
|
||||
default is `true` when `bind_dn` is provided.
|
||||
|
||||
`user_search.pool.size`::
|
||||
The maximum number of connections to the Active Directory server to allow in the
|
||||
connection pool. Defaults to `20`.
|
||||
|
||||
`user_search.pool.initial_size`::
|
||||
The initial number of connections to create to the Active Directory server on startup.
|
||||
Defaults to `0`.
|
||||
|
||||
`user_search.pool.health_check.enabled`::
|
||||
Flag to enable or disable a health check on Active Directory connections in the connection
|
||||
pool. Connections are checked in the background at the specified interval.
|
||||
Defaults to `true`.
|
||||
|
||||
`user_search.pool.health_check.dn`::
|
||||
The distinguished name to be retrieved as part of the health check.
|
||||
Defaults to the value of `bind_dn` if it is a distinguished name.
|
||||
|
||||
`user_search.pool.health_check.interval`::
|
||||
The interval to perform background checks of connections in the pool.
|
||||
Defaults to `60s`.
|
||||
|
||||
`group_search.base_dn`::
|
||||
The context to search for groups in which the user has membership. Defaults
|
||||
to the root of the Active Directory domain.
|
||||
|
|
|
@ -24,9 +24,13 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.buildDnFromDomain;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER;
|
||||
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;
|
||||
|
||||
|
||||
class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
||||
|
||||
|
@ -35,11 +39,10 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
|||
private final LdapSearchScope scope;
|
||||
private final boolean ignoreReferralErrors;
|
||||
|
||||
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;
|
||||
ActiveDirectoryGroupsResolver(Settings settings) {
|
||||
this.baseDn = settings.get("group_search.base_dn", buildDnFromDomain(settings.get(AD_DOMAIN_NAME_SETTING)));
|
||||
this.scope = LdapSearchScope.resolve(settings.get("group_search.scope"), LdapSearchScope.SUB_TREE);
|
||||
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,8 +8,12 @@ package org.elasticsearch.xpack.security.authc.ldap;
|
|||
import com.unboundid.ldap.sdk.Filter;
|
||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
||||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import com.unboundid.ldap.sdk.LDAPInterface;
|
||||
import com.unboundid.ldap.sdk.SearchResultEntry;
|
||||
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
||||
import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
|
@ -27,6 +31,7 @@ import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
|||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
@ -46,7 +51,7 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear
|
|||
* user entry in Active Directory that matches the user name). This eliminates the need for user templates, and simplifies
|
||||
* the configuration for windows admins that may not be familiar with LDAP concepts.
|
||||
*/
|
||||
class ActiveDirectorySessionFactory extends SessionFactory {
|
||||
class ActiveDirectorySessionFactory extends PoolingSessionFactory {
|
||||
|
||||
static final String AD_DOMAIN_NAME_SETTING = "domain_name";
|
||||
|
||||
|
@ -58,29 +63,42 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
static final String AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING = "user_search.down_level_filter";
|
||||
static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope";
|
||||
private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})";
|
||||
private static final Setting<Boolean> POOL_ENABLED = Setting.boolSetting("user_search.pool.enabled",
|
||||
settings -> Boolean.toString(PoolingSessionFactory.BIND_DN.exists(settings)), Setting.Property.NodeScope);
|
||||
|
||||
final DefaultADAuthenticator defaultADAuthenticator;
|
||||
final DownLevelADAuthenticator downLevelADAuthenticator;
|
||||
final UpnADAuthenticator upnADAuthenticator;
|
||||
|
||||
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) {
|
||||
super(config, sslService);
|
||||
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) throws LDAPException {
|
||||
super(config, sslService, new ActiveDirectoryGroupsResolver(config.settings()), POOL_ENABLED, () -> {
|
||||
if (BIND_DN.exists(config.settings())) {
|
||||
return new SimpleBindRequest(getBindDN(config.settings()), BIND_PASSWORD.get(config.settings()));
|
||||
} else {
|
||||
return new SimpleBindRequest();
|
||||
}
|
||||
}, () -> {
|
||||
if (BIND_DN.exists(config.settings())) {
|
||||
final String healthCheckDn = BIND_DN.get(config.settings());
|
||||
if (healthCheckDn.isEmpty() && healthCheckDn.indexOf('=') > 0) {
|
||||
return healthCheckDn;
|
||||
}
|
||||
}
|
||||
return config.settings().get(AD_USER_SEARCH_BASEDN_SETTING, config.settings().get(AD_DOMAIN_NAME_SETTING));
|
||||
});
|
||||
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,
|
||||
ignoreReferralErrors);
|
||||
LdapMetaDataResolver metaDataResolver = new LdapMetaDataResolver(config.settings(), ignoreReferralErrors);
|
||||
defaultADAuthenticator = new DefaultADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||
defaultADAuthenticator = new DefaultADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||
metaDataResolver, domainDN);
|
||||
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||
metaDataResolver, domainDN, sslService);
|
||||
upnADAuthenticator = new UpnADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||
metaDataResolver, domainDN);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,30 +106,78 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
return new String[] {"ldap://" + settings.get(AD_DOMAIN_NAME_SETTING) + ":389"};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an active directory bind that looks up the user DN after binding with a windows principal.
|
||||
*
|
||||
* @param username name of the windows user without the domain
|
||||
*/
|
||||
@Override
|
||||
public void session(String username, SecureString password, ActionListener<LdapSession> listener) {
|
||||
void getSessionWithPool(LDAPConnectionPool connectionPool, String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
getADAuthenticator(user).authenticate(connectionPool, user, password, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
void getSessionWithoutPool(String username, SecureString password, ActionListener<LdapSession> listener) {
|
||||
// the runnable action here allows us make the control/flow logic simpler to understand. If we got a connection then lets
|
||||
// authenticate. If there was a failure pass it back using the listener
|
||||
Runnable runnable;
|
||||
try {
|
||||
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
||||
runnable = () -> getADAuthenticator(username).authenticate(connection, username, password,
|
||||
ActionListener.wrap(listener::onResponse,
|
||||
(e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}));
|
||||
ActionListener.wrap(listener::onResponse,
|
||||
(e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}));
|
||||
} catch (LDAPException e) {
|
||||
runnable = () -> listener.onFailure(e);
|
||||
}
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
void getUnauthenticatedSessionWithPool(LDAPConnectionPool connectionPool, String user, ActionListener<LdapSession> listener) {
|
||||
getADAuthenticator(user).searchForDN(connectionPool, user, null, Math.toIntExact(timeout.seconds()), ActionListener.wrap(entry -> {
|
||||
if (entry == null) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
final String dn = entry.getDN();
|
||||
listener.onResponse(new LdapSession(logger, config, connectionPool, dn, groupResolver, metaDataResolver, timeout, null));
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
@Override
|
||||
void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener) {
|
||||
if (BIND_DN.exists(config.settings())) {
|
||||
LDAPConnection connection = null;
|
||||
boolean startedSearching = false;
|
||||
try {
|
||||
connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
||||
connection.bind(new SimpleBindRequest(getBindDN(config.settings()), BIND_PASSWORD.get(config.settings())));
|
||||
final LDAPConnection finalConnection = connection;
|
||||
getADAuthenticator(user).searchForDN(finalConnection, user, null, Math.toIntExact(timeout.getSeconds()),
|
||||
ActionListener.wrap(entry -> {
|
||||
if (entry == null) {
|
||||
IOUtils.closeWhileHandlingException(finalConnection);
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
final String dn = entry.getDN();
|
||||
listener.onResponse(new LdapSession(logger, config, finalConnection, dn, groupResolver, metaDataResolver,
|
||||
timeout, null));
|
||||
}
|
||||
}, e -> {
|
||||
IOUtils.closeWhileHandlingException(finalConnection);
|
||||
listener.onFailure(e);
|
||||
}));
|
||||
startedSearching = true;
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
if (connection != null && startedSearching == false) {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param domain active directory domain name
|
||||
* @return LDAP DN, distinguished name, of the root of the domain
|
||||
|
@ -120,6 +186,14 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
return "DC=" + domain.replace(".", ",DC=");
|
||||
}
|
||||
|
||||
static String getBindDN(Settings settings) {
|
||||
String bindDN = BIND_DN.get(settings);
|
||||
if (bindDN.isEmpty() == false && bindDN.indexOf('\\') < 0 && bindDN.indexOf('@') < 0) {
|
||||
bindDN = bindDN + "@" + settings.get(AD_DOMAIN_NAME_SETTING);
|
||||
}
|
||||
return bindDN;
|
||||
}
|
||||
|
||||
public static Set<Setting<?>> getSettings() {
|
||||
Set<Setting<?>> settings = new HashSet<>();
|
||||
settings.addAll(SessionFactory.getSettings());
|
||||
|
@ -131,6 +205,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
settings.add(Setting.simpleString(AD_UPN_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope));
|
||||
settings.add(Setting.simpleString(AD_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope));
|
||||
settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope));
|
||||
settings.addAll(PoolingSessionFactory.getSettings());
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
@ -154,6 +229,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
final String userSearchDN;
|
||||
final LdapSearchScope userSearchScope;
|
||||
final String userSearchFilter;
|
||||
final String bindDN;
|
||||
final String bindPassword; // TODO this needs to be a setting in the secure settings store!
|
||||
|
||||
ADAuthenticator(RealmConfig realm, TimeValue timeout, boolean ignoreReferralErrors, Logger logger,
|
||||
GroupsResolver groupsResolver, LdapMetaDataResolver metaDataResolver, String domainDN,
|
||||
|
@ -165,6 +242,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
this.groupsResolver = groupsResolver;
|
||||
this.metaDataResolver = metaDataResolver;
|
||||
final Settings settings = realm.settings();
|
||||
this.bindDN = getBindDN(settings);
|
||||
this.bindPassword = BIND_PASSWORD.get(settings);
|
||||
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
||||
userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE);
|
||||
userSearchFilter = settings.get(userSearchFilterSetting, defaultUserSearchFilter);
|
||||
|
@ -174,7 +253,11 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
ActionListener<LdapSession> listener) {
|
||||
boolean success = false;
|
||||
try {
|
||||
connection.bind(bindUsername(username), new String(password.getChars()));
|
||||
connection.bind(new SimpleBindRequest(bindUsername(username), CharArrays.toUtf8Bytes(password.getChars()),
|
||||
new AuthorizationIdentityRequestControl()));
|
||||
if (bindDN.isEmpty() == false) {
|
||||
connection.bind(new SimpleBindRequest(bindDN, bindPassword));
|
||||
}
|
||||
searchForDN(connection, username, password, Math.toIntExact(timeout.seconds()), ActionListener.wrap((entry) -> {
|
||||
if (entry == null) {
|
||||
IOUtils.close(connection);
|
||||
|
@ -200,6 +283,28 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
final void authenticate(LDAPConnectionPool pool, String username, SecureString password,
|
||||
ActionListener<LdapSession> listener) {
|
||||
try {
|
||||
LdapUtils.privilegedConnect(() -> {
|
||||
SimpleBindRequest request = new SimpleBindRequest(bindUsername(username), CharArrays.toUtf8Bytes(password.getChars()));
|
||||
return pool.bindAndRevertAuthentication(request);
|
||||
});
|
||||
searchForDN(pool, username, password, Math.toIntExact(timeout.seconds()), ActionListener.wrap((entry) -> {
|
||||
if (entry == null) {
|
||||
// we did not find the user, cannot authenticate in this realm
|
||||
listener.onFailure(new ElasticsearchSecurityException("search for user [" + username
|
||||
+ "] by principle name yielded no results"));
|
||||
} else {
|
||||
final String dn = entry.getDN();
|
||||
listener.onResponse(new LdapSession(logger, realm, pool, dn, groupsResolver, metaDataResolver, timeout, null));
|
||||
}
|
||||
}, listener::onFailure));
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
String bindUsername(String username) {
|
||||
return username;
|
||||
}
|
||||
|
@ -209,7 +314,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
return userSearchFilter;
|
||||
}
|
||||
|
||||
abstract void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,
|
||||
abstract void searchForDN(LDAPInterface connection, String username, SecureString password, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener);
|
||||
}
|
||||
|
||||
|
@ -233,7 +338,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
void searchForDN(LDAPConnection connection, String username, SecureString password,
|
||||
void searchForDN(LDAPInterface connection, String username, SecureString password,
|
||||
int timeLimitSeconds, ActionListener<SearchResultEntry> listener) {
|
||||
try {
|
||||
searchForEntry(connection, userSearchDN, userSearchScope.scope(),
|
||||
|
@ -276,7 +381,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,
|
||||
void searchForDN(LDAPInterface connection, String username, SecureString password, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener) {
|
||||
String[] parts = username.split("\\\\");
|
||||
assert parts.length == 2;
|
||||
|
@ -285,7 +390,6 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
|
||||
netBiosDomainNameToDn(connection, netBiosDomainName, username, password, timeLimitSeconds, ActionListener.wrap((domainDN) -> {
|
||||
if (domainDN == null) {
|
||||
IOUtils.close(connection);
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
try {
|
||||
|
@ -294,75 +398,75 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
accountName), timeLimitSeconds, ignoreReferralErrors,
|
||||
listener, attributesToSearchFor(groupsResolver.attributes()));
|
||||
} catch (LDAPException e) {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
}, (e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}));
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
void netBiosDomainNameToDn(LDAPConnection connection, String netBiosDomainName, String username, SecureString password,
|
||||
void netBiosDomainNameToDn(LDAPInterface ldapInterface, String netBiosDomainName, String username, SecureString password,
|
||||
int timeLimitSeconds, ActionListener<String> listener) {
|
||||
final String cachedName = domainNameCache.get(netBiosDomainName);
|
||||
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
|
||||
// most AD users do not use non-standard ports
|
||||
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
||||
boolean startedSearching = false;
|
||||
LDAPConnection searchConnection = null;
|
||||
try {
|
||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||
if (connection.getSSLSession() != null) {
|
||||
try {
|
||||
if (cachedName != null) {
|
||||
listener.onResponse(cachedName);
|
||||
} else if (usingGlobalCatalog(ldapInterface)) {
|
||||
// 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;
|
||||
LDAPConnection searchConnection = null;
|
||||
LDAPConnection ldapConnection = null;
|
||||
try {
|
||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||
if (ldapInterface instanceof LDAPConnection) {
|
||||
ldapConnection = (LDAPConnection) ldapInterface;
|
||||
} else {
|
||||
ldapConnection = LdapUtils.privilegedConnect(((LDAPConnectionPool) ldapInterface)::getConnection);
|
||||
}
|
||||
final LDAPConnection finalLdapConnection = ldapConnection;
|
||||
searchConnection = LdapUtils.privilegedConnect(
|
||||
() -> new LDAPConnection(connection.getSocketFactory(), options,
|
||||
connection.getConnectedAddress(), 636));
|
||||
} else {
|
||||
searchConnection = LdapUtils.privilegedConnect(() ->
|
||||
new LDAPConnection(options, connection.getConnectedAddress(), 389));
|
||||
() -> new LDAPConnection(finalLdapConnection.getSocketFactory(), options,
|
||||
finalLdapConnection.getConnectedAddress(),
|
||||
finalLdapConnection.getSSLSession() != null ? 636 : 389));
|
||||
|
||||
final SimpleBindRequest bindRequest =
|
||||
bindDN.isEmpty() ? new SimpleBindRequest(username, CharArrays.toUtf8Bytes(password.getChars())) :
|
||||
new SimpleBindRequest(bindDN, bindPassword);
|
||||
searchConnection.bind(bindRequest);
|
||||
final LDAPConnection finalConnection = searchConnection;
|
||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||
(results) -> {
|
||||
IOUtils.close(finalConnection);
|
||||
handleSearchResults(results, netBiosDomainName, domainNameCache, listener);
|
||||
}, (e) -> {
|
||||
IOUtils.closeWhileHandlingException(finalConnection);
|
||||
listener.onFailure(e);
|
||||
}),
|
||||
"ncname");
|
||||
startedSearching = true;
|
||||
} finally {
|
||||
if (startedSearching == false) {
|
||||
IOUtils.closeWhileHandlingException(searchConnection);
|
||||
}
|
||||
if (ldapInterface instanceof LDAPConnectionPool && ldapConnection != null) {
|
||||
((LDAPConnectionPool) ldapInterface).releaseConnection(ldapConnection);
|
||||
}
|
||||
}
|
||||
searchConnection.bind(username, new String(password.getChars()));
|
||||
final LDAPConnection finalConnection = searchConnection;
|
||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||
(results) -> {
|
||||
IOUtils.close(finalConnection);
|
||||
handleSearchResults(results, netBiosDomainName,
|
||||
domainNameCache, listener);
|
||||
}, (e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}),
|
||||
"ncname");
|
||||
startedSearching = true;
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
if (startedSearching == false) {
|
||||
IOUtils.closeWhileHandlingException(searchConnection);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
} else {
|
||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||
search(connection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
search(ldapInterface, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||
(results) -> handleSearchResults(results, netBiosDomainName,
|
||||
domainNameCache, listener),
|
||||
(e) -> {
|
||||
IOUtils.closeWhileHandlingException(connection);
|
||||
listener.onFailure(e);
|
||||
}),
|
||||
listener::onFailure),
|
||||
"ncname");
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
} catch (LDAPException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,15 +489,32 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
static boolean usingGlobalCatalog(Settings settings, LDAPConnection ldapConnection) {
|
||||
Boolean usingGlobalCatalog = settings.getAsBoolean("global_catalog", null);
|
||||
if (usingGlobalCatalog != null) {
|
||||
return usingGlobalCatalog;
|
||||
static boolean usingGlobalCatalog(LDAPInterface ldap) throws LDAPException {
|
||||
if (ldap instanceof LDAPConnection) {
|
||||
return usingGlobalCatalog((LDAPConnection) ldap);
|
||||
} else {
|
||||
LDAPConnectionPool pool = (LDAPConnectionPool) ldap;
|
||||
LDAPConnection connection = null;
|
||||
try {
|
||||
connection = LdapUtils.privilegedConnect(pool::getConnection);
|
||||
return usingGlobalCatalog(connection);
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
pool.releaseConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean usingGlobalCatalog(LDAPConnection ldapConnection) {
|
||||
return ldapConnection.getConnectedPort() == 3268 || ldapConnection.getConnectedPort() == 3269;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates user principal names provided by the user (eq user@domain). Note this authenticator does not currently support
|
||||
* UPN suffixes that are different than the actual domain name.
|
||||
*/
|
||||
static class UpnADAuthenticator extends ADAuthenticator {
|
||||
|
||||
static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))";
|
||||
|
@ -404,7 +525,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
|||
AD_UPN_USER_SEARCH_FILTER_SETTING, UPN_USER_FILTER);
|
||||
}
|
||||
|
||||
void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,
|
||||
void searchForDN(LDAPInterface connection, String username, SecureString password, int timeLimitSeconds,
|
||||
ActionListener<SearchResultEntry> listener) {
|
||||
String[] parts = username.split("@");
|
||||
assert parts.length == 2;
|
||||
|
|
|
@ -6,25 +6,19 @@
|
|||
package org.elasticsearch.xpack.security.authc.ldap;
|
||||
|
||||
import com.unboundid.ldap.sdk.Filter;
|
||||
import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck;
|
||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
|
||||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import com.unboundid.ldap.sdk.LDAPInterface;
|
||||
import com.unboundid.ldap.sdk.SearchResultEntry;
|
||||
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.action.ActionListener;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver;
|
||||
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.LdapSession.GroupsResolver;
|
||||
|
@ -36,7 +30,6 @@ import org.elasticsearch.xpack.ssl.SSLService;
|
|||
import java.util.Arrays;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
@ -44,12 +37,9 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attr
|
|||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter;
|
||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
||||
|
||||
class LdapUserSearchSessionFactory extends SessionFactory {
|
||||
class LdapUserSearchSessionFactory extends PoolingSessionFactory {
|
||||
|
||||
static final int DEFAULT_CONNECTION_POOL_SIZE = 20;
|
||||
static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0;
|
||||
static final String DEFAULT_USERNAME_ATTRIBUTE = "uid";
|
||||
static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L);
|
||||
private static final String DEFAULT_USERNAME_ATTRIBUTE = "uid";
|
||||
|
||||
static final String SEARCH_PREFIX = "user_search.";
|
||||
static final Setting<String> SEARCH_ATTRIBUTE = new Setting<>("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE,
|
||||
|
@ -59,36 +49,22 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
private static final Setting<String> SEARCH_FILTER = Setting.simpleString("user_search.filter", Setting.Property.NodeScope);
|
||||
private static final Setting<LdapSearchScope> SEARCH_SCOPE = new Setting<>("user_search.scope", (String) null,
|
||||
s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope);
|
||||
|
||||
private static final Setting<Boolean> POOL_ENABLED = Setting.boolSetting("user_search.pool.enabled",
|
||||
true, Setting.Property.NodeScope);
|
||||
private static final Setting<Integer> POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size",
|
||||
DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope);
|
||||
private static final Setting<Integer> POOL_SIZE = Setting.intSetting("user_search.pool.size",
|
||||
DEFAULT_CONNECTION_POOL_SIZE, 1, Setting.Property.NodeScope);
|
||||
private static final Setting<TimeValue> HEALTH_CHECK_INTERVAL = Setting.timeSetting("user_search.pool.health_check.interval",
|
||||
DEFAULT_HEALTH_CHECK_INTERVAL, Setting.Property.NodeScope);
|
||||
private static final Setting<Boolean> HEALTH_CHECK_ENABLED = Setting.boolSetting("user_search.pool.health_check.enabled",
|
||||
true, Setting.Property.NodeScope);
|
||||
private static final Setting<Optional<String>> HEALTH_CHECK_DN = new Setting<>("user_search.pool.health_check.dn", (String) null,
|
||||
Optional::ofNullable, Setting.Property.NodeScope);
|
||||
|
||||
private static final Setting<String> BIND_DN = Setting.simpleString("bind_dn",
|
||||
Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
private static final Setting<String> BIND_PASSWORD = Setting.simpleString("bind_password",
|
||||
Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
private static final Setting<Boolean> POOL_ENABLED = Setting.boolSetting("user_search.pool.enabled", true, Setting.Property.NodeScope);
|
||||
|
||||
private final String userSearchBaseDn;
|
||||
private final LdapSearchScope scope;
|
||||
private final String searchFilter;
|
||||
private final GroupsResolver groupResolver;
|
||||
private final boolean useConnectionPool;
|
||||
|
||||
private final LDAPConnectionPool connectionPool;
|
||||
private final LdapMetaDataResolver metaDataResolver;
|
||||
|
||||
LdapUserSearchSessionFactory(RealmConfig config, SSLService sslService) throws LDAPException {
|
||||
super(config, sslService);
|
||||
super(config, sslService, groupResolver(config.settings()), POOL_ENABLED,
|
||||
() -> LdapUserSearchSessionFactory.bindRequest(config.settings()),
|
||||
() -> {
|
||||
if (BIND_DN.exists(config.settings())) {
|
||||
return BIND_DN.get(config.settings());
|
||||
} else {
|
||||
return SEARCH_BASE_DN.get(config.settings());
|
||||
}
|
||||
});
|
||||
Settings settings = config.settings();
|
||||
if (SEARCH_BASE_DN.exists(settings)) {
|
||||
userSearchBaseDn = SEARCH_BASE_DN.get(settings);
|
||||
|
@ -97,57 +73,10 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
}
|
||||
scope = SEARCH_SCOPE.get(settings);
|
||||
searchFilter = getSearchFilter(config);
|
||||
groupResolver = groupResolver(settings);
|
||||
metaDataResolver = new LdapMetaDataResolver(config.settings(), ignoreReferralErrors);
|
||||
useConnectionPool = POOL_ENABLED.get(settings);
|
||||
if (useConnectionPool) {
|
||||
connectionPool = createConnectionPool(config, serverSet, timeout, logger);
|
||||
} else {
|
||||
connectionPool = null;
|
||||
}
|
||||
logger.info("Realm [{}] is in user-search mode - base_dn=[{}], search filter=[{}]",
|
||||
config.name(), userSearchBaseDn, searchFilter);
|
||||
}
|
||||
|
||||
static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, Logger logger)
|
||||
throws LDAPException {
|
||||
Settings settings = config.settings();
|
||||
SimpleBindRequest bindRequest = bindRequest(settings);
|
||||
final int initialSize = POOL_INITIAL_SIZE.get(settings);
|
||||
final int size = POOL_SIZE.get(settings);
|
||||
LDAPConnectionPool pool = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
pool = LdapUtils.privilegedConnect(() -> new LDAPConnectionPool(serverSet, bindRequest, initialSize, size));
|
||||
pool.setRetryFailedOperationsDueToInvalidConnections(true);
|
||||
if (HEALTH_CHECK_ENABLED.get(settings)) {
|
||||
String entryDn = HEALTH_CHECK_DN.get(settings).orElseGet(() -> bindRequest == null ? null : bindRequest.getBindDN());
|
||||
final long healthCheckInterval = HEALTH_CHECK_INTERVAL.get(settings).millis();
|
||||
if (entryDn != null) {
|
||||
// Checks the status of the LDAP connection at a specified interval in the background. We do not check on
|
||||
// on create as the LDAP server may require authentication to get an entry and a bind request has not been executed
|
||||
// yet so we could end up never getting a connection. We do not check on checkout as we always set retry operations
|
||||
// and the pool will handle a bad connection without the added latency on every operation
|
||||
LDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(),
|
||||
false, false, false, true, false);
|
||||
pool.setHealthCheck(healthCheck);
|
||||
pool.setHealthCheckIntervalMillis(healthCheckInterval);
|
||||
} else {
|
||||
logger.warn("[" + RealmSettings.getFullSettingKey(config, BIND_DN) + "] and [" +
|
||||
RealmSettings.getFullSettingKey(config, HEALTH_CHECK_DN) + "] have not been specified so no " +
|
||||
"ldap query will be run as a health check");
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
return pool;
|
||||
} finally {
|
||||
if (success == false && pool != null) {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SimpleBindRequest bindRequest(Settings settings) {
|
||||
if (BIND_DN.exists(settings)) {
|
||||
return new SimpleBindRequest(BIND_DN.get(settings), BIND_PASSWORD.get(settings));
|
||||
|
@ -156,23 +85,15 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean hasUserSearchSettings(RealmConfig config) {
|
||||
static boolean hasUserSearchSettings(RealmConfig config) {
|
||||
return config.settings().getByPrefix("user_search.").isEmpty() == false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void session(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
if (useConnectionPool) {
|
||||
getSessionWithPool(user, password, listener);
|
||||
} else {
|
||||
getSessionWithoutPool(user, password, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a LDAPSession using the connection pool that potentially holds existing connections to the server
|
||||
*/
|
||||
private void getSessionWithPool(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
@Override
|
||||
void getSessionWithPool(LDAPConnectionPool connectionPool, String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
findUser(user, connectionPool, ActionListener.wrap((entry) -> {
|
||||
if (entry == null) {
|
||||
listener.onResponse(null);
|
||||
|
@ -204,7 +125,8 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
* <li>Creates a new LDAPSession with the bound connection</li>
|
||||
* </ol>
|
||||
*/
|
||||
private void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
@Override
|
||||
void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
boolean success = false;
|
||||
LDAPConnection connection = null;
|
||||
try {
|
||||
|
@ -261,33 +183,42 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void unauthenticatedSession(String user, ActionListener<LdapSession> listener) {
|
||||
void getUnauthenticatedSessionWithPool(LDAPConnectionPool connectionPool, String user, ActionListener<LdapSession> listener) {
|
||||
findUser(user, connectionPool, ActionListener.wrap((entry) -> {
|
||||
if (entry == null) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
final String dn = entry.getDN();
|
||||
LdapSession session = new LdapSession(logger, config, connectionPool, dn, groupResolver, metaDataResolver, timeout,
|
||||
entry.getAttributes());
|
||||
listener.onResponse(session);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
@Override
|
||||
void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener) {
|
||||
LDAPConnection connection = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
final LDAPInterface ldapInterface;
|
||||
if (useConnectionPool) {
|
||||
ldapInterface = connectionPool;
|
||||
} else {
|
||||
connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
||||
connection.bind(bindRequest(config.settings()));
|
||||
ldapInterface = connection;
|
||||
}
|
||||
connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
||||
connection.bind(bindRequest(config.settings()));
|
||||
final LDAPConnection finalConnection = connection;
|
||||
|
||||
findUser(user, ldapInterface, ActionListener.wrap((entry) -> {
|
||||
findUser(user, finalConnection, ActionListener.wrap((entry) -> {
|
||||
if (entry == null) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
boolean sessionCreated = false;
|
||||
try {
|
||||
final String dn = entry.getDN();
|
||||
LdapSession session = new LdapSession(logger, config, ldapInterface, dn, groupResolver, metaDataResolver, timeout,
|
||||
LdapSession session = new LdapSession(logger, config, finalConnection, dn, groupResolver, metaDataResolver, timeout,
|
||||
entry.getAttributes());
|
||||
sessionCreated = true;
|
||||
listener.onResponse(session);
|
||||
} finally {
|
||||
if (sessionCreated == false && useConnectionPool == false) {
|
||||
IOUtils.close((LDAPConnection) ldapInterface);
|
||||
if (sessionCreated == false) {
|
||||
IOUtils.close(finalConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -316,16 +247,7 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
attributesToSearchFor(groupResolver.attributes(), metaDataResolver.attributeNames()));
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is used to cleanup the connections
|
||||
*/
|
||||
void shutdown() {
|
||||
if (connectionPool != null) {
|
||||
connectionPool.close();
|
||||
}
|
||||
}
|
||||
|
||||
static GroupsResolver groupResolver(Settings settings) {
|
||||
private static GroupsResolver groupResolver(Settings settings) {
|
||||
if (SearchGroupsResolver.BASE_DN.exists(settings)) {
|
||||
return new SearchGroupsResolver(settings);
|
||||
}
|
||||
|
@ -352,17 +274,11 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
public static Set<Setting<?>> getSettings() {
|
||||
Set<Setting<?>> settings = new HashSet<>();
|
||||
settings.addAll(SessionFactory.getSettings());
|
||||
settings.addAll(PoolingSessionFactory.getSettings());
|
||||
settings.add(SEARCH_BASE_DN);
|
||||
settings.add(SEARCH_SCOPE);
|
||||
settings.add(SEARCH_ATTRIBUTE);
|
||||
settings.add(POOL_ENABLED);
|
||||
settings.add(POOL_INITIAL_SIZE);
|
||||
settings.add(POOL_SIZE);
|
||||
settings.add(HEALTH_CHECK_ENABLED);
|
||||
settings.add(HEALTH_CHECK_DN);
|
||||
settings.add(HEALTH_CHECK_INTERVAL);
|
||||
settings.add(BIND_DN);
|
||||
settings.add(BIND_PASSWORD);
|
||||
settings.add(SEARCH_FILTER);
|
||||
|
||||
settings.addAll(SearchGroupsResolver.getSettings());
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 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 com.unboundid.ldap.sdk.BindRequest;
|
||||
import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
||||
import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
|
||||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import com.unboundid.ldap.sdk.ServerSet;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.lease.Releasable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Base class for LDAP session factories that can make use of a connection pool
|
||||
*/
|
||||
abstract class PoolingSessionFactory extends SessionFactory implements Releasable {
|
||||
|
||||
static final int DEFAULT_CONNECTION_POOL_SIZE = 20;
|
||||
static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0;
|
||||
static final Setting<String> BIND_DN = Setting.simpleString("bind_dn", Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
static final Setting<String> BIND_PASSWORD = Setting.simpleString("bind_password", Setting.Property.NodeScope,
|
||||
Setting.Property.Filtered);
|
||||
|
||||
private static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L);
|
||||
private static final Setting<Integer> POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size",
|
||||
DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope);
|
||||
private static final Setting<Integer> POOL_SIZE = Setting.intSetting("user_search.pool.size",
|
||||
DEFAULT_CONNECTION_POOL_SIZE, 1, Setting.Property.NodeScope);
|
||||
private static final Setting<TimeValue> HEALTH_CHECK_INTERVAL = Setting.timeSetting("user_search.pool.health_check.interval",
|
||||
DEFAULT_HEALTH_CHECK_INTERVAL, Setting.Property.NodeScope);
|
||||
private static final Setting<Boolean> HEALTH_CHECK_ENABLED = Setting.boolSetting("user_search.pool.health_check.enabled",
|
||||
true, Setting.Property.NodeScope);
|
||||
private static final Setting<Optional<String>> HEALTH_CHECK_DN = new Setting<>("user_search.pool.health_check.dn", (String) null,
|
||||
Optional::ofNullable, Setting.Property.NodeScope);
|
||||
|
||||
private final boolean useConnectionPool;
|
||||
private final LDAPConnectionPool connectionPool;
|
||||
|
||||
final LdapMetaDataResolver metaDataResolver;
|
||||
final LdapSession.GroupsResolver groupResolver;
|
||||
|
||||
|
||||
/**
|
||||
* @param config the configuration for the realm
|
||||
* @param sslService the ssl service to get a socket factory or context from
|
||||
* @param groupResolver the resolver to use to find groups belonging to a user
|
||||
* @param poolingEnabled the setting that should be used to determine if connection pooling is enabled
|
||||
* @param bindRequestSupplier the supplier for a bind requests that should be used for pooled connections
|
||||
* @param healthCheckDNSupplier a supplier for the dn to query for health checks
|
||||
*/
|
||||
PoolingSessionFactory(RealmConfig config, SSLService sslService, LdapSession.GroupsResolver groupResolver,
|
||||
Setting<Boolean> poolingEnabled, Supplier<BindRequest> bindRequestSupplier,
|
||||
Supplier<String> healthCheckDNSupplier) throws LDAPException {
|
||||
super(config, sslService);
|
||||
this.groupResolver = groupResolver;
|
||||
this.metaDataResolver = new LdapMetaDataResolver(config.settings(), ignoreReferralErrors);
|
||||
this.useConnectionPool = poolingEnabled.get(config.settings());
|
||||
if (useConnectionPool) {
|
||||
this.connectionPool = createConnectionPool(config, serverSet, timeout, logger, bindRequestSupplier, healthCheckDNSupplier);
|
||||
} else {
|
||||
this.connectionPool = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void session(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||
if (useConnectionPool) {
|
||||
getSessionWithPool(connectionPool, user, password, listener);
|
||||
} else {
|
||||
getSessionWithoutPool(user, password, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void unauthenticatedSession(String user, ActionListener<LdapSession> listener) {
|
||||
if (useConnectionPool) {
|
||||
getUnauthenticatedSessionWithPool(connectionPool, user, listener);
|
||||
} else {
|
||||
getUnauthenticatedSessionWithoutPool(user, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get a {@link LdapSession} using the provided credentials and makes use of the provided connection pool
|
||||
*/
|
||||
abstract void getSessionWithPool(LDAPConnectionPool connectionPool, String user, SecureString password,
|
||||
ActionListener<LdapSession> listener);
|
||||
|
||||
/**
|
||||
* Attempts to get a {@link LdapSession} using the provided credentials and opens a new connection to the ldap server
|
||||
*/
|
||||
abstract void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener);
|
||||
|
||||
/**
|
||||
* Attempts to search using a pooled connection for the user and provides an unauthenticated {@link LdapSession} to the listener if the
|
||||
* user is found
|
||||
*/
|
||||
abstract void getUnauthenticatedSessionWithPool(LDAPConnectionPool connectionPool, String user, ActionListener<LdapSession> listener);
|
||||
|
||||
/**
|
||||
* Attempts to search using a new connection for the user and provides an unauthenticated {@link LdapSession} to the listener if the
|
||||
* user is found
|
||||
*/
|
||||
abstract void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener);
|
||||
|
||||
/**
|
||||
* Creates the connection pool that will be used by the session factory and initializes the health check support
|
||||
*/
|
||||
static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, Logger logger,
|
||||
Supplier<BindRequest> bindRequestSupplier,
|
||||
Supplier<String> healthCheckDnSupplier) throws LDAPException {
|
||||
Settings settings = config.settings();
|
||||
BindRequest bindRequest = bindRequestSupplier.get();
|
||||
final int initialSize = POOL_INITIAL_SIZE.get(settings);
|
||||
final int size = POOL_SIZE.get(settings);
|
||||
LDAPConnectionPool pool = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
pool = LdapUtils.privilegedConnect(() -> new LDAPConnectionPool(serverSet, bindRequest, initialSize, size));
|
||||
pool.setRetryFailedOperationsDueToInvalidConnections(true);
|
||||
if (HEALTH_CHECK_ENABLED.get(settings)) {
|
||||
String entryDn = HEALTH_CHECK_DN.get(settings).orElseGet(healthCheckDnSupplier);
|
||||
final long healthCheckInterval = HEALTH_CHECK_INTERVAL.get(settings).millis();
|
||||
if (entryDn != null) {
|
||||
// Checks the status of the LDAP connection at a specified interval in the background. We do not check on
|
||||
// create as the LDAP server may require authentication to get an entry and a bind request has not been executed
|
||||
// yet so we could end up never getting a connection. We do not check on checkout as we always set retry operations
|
||||
// and the pool will handle a bad connection without the added latency on every operation
|
||||
LDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(),
|
||||
false, false, false, true, false);
|
||||
pool.setHealthCheck(healthCheck);
|
||||
pool.setHealthCheckIntervalMillis(healthCheckInterval);
|
||||
} else {
|
||||
logger.warn(new ParameterizedMessage("[{}] and [{}} have not been specified or are not valid distinguished names," +
|
||||
"so connection health checking is disabled", RealmSettings.getFullSettingKey(config, BIND_DN),
|
||||
RealmSettings.getFullSettingKey(config, HEALTH_CHECK_DN)));
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
return pool;
|
||||
} finally {
|
||||
if (success == false && pool != null) {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to cleanup the connection pool if one is being used
|
||||
*/
|
||||
@Override
|
||||
public final void close() {
|
||||
if (connectionPool != null) {
|
||||
connectionPool.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Setting<?>> getSettings() {
|
||||
return Sets.newHashSet(POOL_INITIAL_SIZE, POOL_SIZE, HEALTH_CHECK_ENABLED, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_DN, BIND_DN,
|
||||
BIND_PASSWORD);
|
||||
}
|
||||
}
|
|
@ -48,8 +48,7 @@ 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);
|
||||
|
||||
|
|
|
@ -29,10 +29,11 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
|
||||
public void testResolveSubTree() throws Exception {
|
||||
Settings settings = Settings.builder()
|
||||
.put("scope", LdapSearchScope.SUB_TREE)
|
||||
.put("group_search.scope", LdapSearchScope.SUB_TREE)
|
||||
.put("group_search.base_dn", "DC=ad,DC=test,DC=elasticsearch,DC=com")
|
||||
.put("domain_name", "ad.test.elasticsearch.com")
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
|
@ -48,10 +49,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
public void testResolveOneLevel() throws Exception {
|
||||
Settings settings = Settings.builder()
|
||||
.put("scope", LdapSearchScope.ONE_LEVEL)
|
||||
.put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com")
|
||||
.put("group_search.base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com")
|
||||
.put("domain_name", "ad.test.elasticsearch.com")
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, hasItem(containsString("Users")));
|
||||
|
@ -59,11 +60,11 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
|||
|
||||
public void testResolveBaseLevel() throws Exception {
|
||||
Settings settings = Settings.builder()
|
||||
.put("scope", LdapSearchScope.BASE)
|
||||
.put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
||||
.put("group_search.scope", LdapSearchScope.BASE)
|
||||
.put("group_search.base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
||||
.put("domain_name", "ad.test.elasticsearch.com")
|
||||
.build();
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||
assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin")));
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
|||
import org.elasticsearch.test.junit.annotations.Network;
|
||||
import org.elasticsearch.xpack.ssl.VerificationMode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
|
@ -46,43 +47,45 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
RealmConfig config = new RealmConfig("ad-test",
|
||||
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false),
|
||||
globalSettings, new ThreadContext(Settings.EMPTY));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config,
|
||||
sslService);
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("Billionaire"),
|
||||
containsString("Playboy"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
String userName = "ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("Billionaire"),
|
||||
containsString("Playboy"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testNetbiosAuth() throws Exception {
|
||||
final String adUrl = randomFrom("ldap://54.213.145.20:3268", "ldaps://54.213.145.20:3269", AD_LDAP_URL);
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings,
|
||||
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "ades\\ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("Billionaire"),
|
||||
containsString("Playboy"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
String userName = "ades\\ironman";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("Billionaire"),
|
||||
containsString("Playboy"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,23 +97,27 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put("ssl.verification_mode", VerificationMode.CERTIFICATE)
|
||||
.put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms")
|
||||
.build();
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
||||
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"));
|
||||
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAdAuthAvengers() throws Exception {
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings,
|
||||
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
|
||||
for(String user: users) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
assertThat("group avenger test for user "+user, groups(ldap), hasItem(containsString("Avengers")));
|
||||
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow"};
|
||||
for (String user : users) {
|
||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||
assertThat("group avenger test for user " + user, groups(ldap), hasItem(containsString("Avengers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,21 +126,23 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
public void testAuthenticate() throws Exception {
|
||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||
LdapSearchScope.ONE_LEVEL, false);
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,21 +150,23 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
public void testAuthenticateBaseUserSearch() throws Exception {
|
||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||
LdapSearchScope.BASE, false);
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings),
|
||||
new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users"),
|
||||
containsString("Supers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,14 +178,16 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
"CN=Avengers,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com")
|
||||
.put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE)
|
||||
.build();
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
String userName = "hulk";
|
||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
|
||||
assertThat(groups, hasItem(containsString("Avengers")));
|
||||
assertThat(groups, hasItem(containsString("Avengers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,37 +195,41 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
public void testAuthenticateWithUserPrincipalName() throws Exception {
|
||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||
LdapSearchScope.ONE_LEVEL, false);
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
//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", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users")));
|
||||
//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", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testAuthenticateWithSAMAccountName() throws Exception {
|
||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||
LdapSearchScope.ONE_LEVEL, false);
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
//login with sAMAccountName
|
||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
try (LdapSession ldap = session(sessionFactory, "selvig", SECURED_PASSWORD)) {
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
//login with sAMAccountName
|
||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
try (LdapSession ldap = session(sessionFactory, "selvig", SECURED_PASSWORD)) {
|
||||
assertThat(ldap.userDn(), is(userDN));
|
||||
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users")));
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Geniuses"),
|
||||
containsString("CN=Users,CN=Builtin"),
|
||||
containsString("Domain Users")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,16 +241,18 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_FILTER_SETTING,
|
||||
"(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))")
|
||||
.build();
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
//Login with the UserPrincipalName
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("CN=Geniuses"),
|
||||
containsString("CN=Domain Users"),
|
||||
containsString("CN=Users,CN=Builtin")));
|
||||
//Login with the UserPrincipalName
|
||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||
List<String> groups = groups(ldap);
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("CN=Geniuses"),
|
||||
containsString("CN=Domain Users"),
|
||||
containsString("CN=Users,CN=Builtin")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,7 +275,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put("ssl.truststore.password", "changeit")
|
||||
.build();
|
||||
}
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings),
|
||||
new ThreadContext(globalSettings));
|
||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
|
@ -290,7 +310,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put("ssl.truststore.password", "changeit")
|
||||
.build();
|
||||
}
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings),
|
||||
new ThreadContext(globalSettings));
|
||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
|
@ -318,7 +339,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put("ssl.truststore.password", "changeit")
|
||||
.build();
|
||||
}
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings, new Environment(globalSettings),
|
||||
new ThreadContext(globalSettings));
|
||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
|
@ -334,16 +356,19 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
}
|
||||
|
||||
public void testAdAuthWithHostnameVerification() throws Exception {
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
||||
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings,
|
||||
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
String userName = "ironman";
|
||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||
() -> 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();
|
||||
assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated")));
|
||||
String userName = "ironman";
|
||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||
() -> 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();
|
||||
assertThat(expected.getMessage(),
|
||||
anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated")));
|
||||
}
|
||||
}
|
||||
|
||||
public void testStandardLdapHostnameVerification() throws Exception {
|
||||
|
@ -353,7 +378,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
.put(LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
|
||||
.put("ssl.verification_mode", VerificationMode.FULL)
|
||||
.build();
|
||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
RealmConfig config =
|
||||
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
|
@ -365,7 +391,30 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated")));
|
||||
}
|
||||
|
||||
Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) {
|
||||
public void testADLookup() throws Exception {
|
||||
RealmConfig config = new RealmConfig("ad-test",
|
||||
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false, true),
|
||||
globalSettings, new ThreadContext(Settings.EMPTY));
|
||||
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||
|
||||
List<String> users = randomSubsetOf(Arrays.asList("cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow",
|
||||
"cap@ad.test.elasticsearch.com", "hawkeye@ad.test.elasticsearch.com", "hulk@ad.test.elasticsearch.com",
|
||||
"ironman@ad.test.elasticsearch.com", "thor@ad.test.elasticsearch.com", "blackwidow@ad.test.elasticsearch.com",
|
||||
"ADES\\cap", "ADES\\hawkeye", "ADES\\hulk", "ADES\\ironman", "ADES\\thor", "ADES\\blackwidow"));
|
||||
for (String user : users) {
|
||||
try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) {
|
||||
assertNotNull("ldap session was null for user " + user, ldap);
|
||||
assertThat("group avenger test for user " + user, groups(ldap), hasItem(containsString("Avengers")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) {
|
||||
return buildAdSettings(ldapUrl, adDomainName, hostnameVerification, randomBoolean());
|
||||
}
|
||||
|
||||
private Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification, boolean useBindUser) {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(ActiveDirectorySessionFactory.URLS_SETTING, ldapUrl)
|
||||
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, adDomainName);
|
||||
|
@ -374,10 +423,23 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
} else {
|
||||
builder.put(ActiveDirectorySessionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification);
|
||||
}
|
||||
|
||||
if (useGlobalSSL == false) {
|
||||
builder.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
|
||||
.put("ssl.truststore.password", "changeit");
|
||||
}
|
||||
|
||||
if (useBindUser) {
|
||||
final String user = randomFrom("cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", "cap@ad.test.elasticsearch.com",
|
||||
"hawkeye@ad.test.elasticsearch.com", "hulk@ad.test.elasticsearch.com", "ironman@ad.test.elasticsearch.com",
|
||||
"thor@ad.test.elasticsearch.com", "blackwidow@ad.test.elasticsearch.com", "ADES\\cap", "ADES\\hawkeye", "ADES\\hulk",
|
||||
"ADES\\ironman", "ADES\\thor", "ADES\\blackwidow", "CN=Bruce Banner,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com");
|
||||
final boolean poolingEnabled = randomBoolean();
|
||||
builder.put("bind_dn", user)
|
||||
.put("bind_password", PASSWORD)
|
||||
.put("user_search.pool.enabled", poolingEnabled);
|
||||
logger.info("using bind user [{}] with pooling enabled [{}]", user, poolingEnabled);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -387,6 +449,12 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
|||
return future.actionGet();
|
||||
}
|
||||
|
||||
private LdapSession unauthenticatedSession(SessionFactory factory, String username) {
|
||||
PlainActionFuture<LdapSession> future = new PlainActionFuture<>();
|
||||
factory.unauthenticatedSession(username, future);
|
||||
return future.actionGet();
|
||||
}
|
||||
|
||||
private List<String> groups(LdapSession ldapSession) {
|
||||
PlainActionFuture<List<String>> future = new PlainActionFuture<>();
|
||||
ldapSession.groups(future);
|
||||
|
|
|
@ -230,7 +230,7 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
try {
|
||||
assertThat(sessionFactory, is(instanceOf(LdapUserSearchSessionFactory.class)));
|
||||
} finally {
|
||||
((LdapUserSearchSessionFactory)sessionFactory).shutdown();
|
||||
((LdapUserSearchSessionFactory)sessionFactory).close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
try {
|
||||
assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true));
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -140,7 +140,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -177,7 +177,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertNull(session(sessionFactory, user, userPass));
|
||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -223,7 +223,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -260,7 +260,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertNull(session(sessionFactory, user, userPass));
|
||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -306,7 +306,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -342,7 +342,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertNull(session(sessionFactory, user, userPass));
|
||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
|
||||
if (useAttribute) {
|
||||
|
@ -380,7 +380,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
assertThat(dn, containsString("William Bush"));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,7 +434,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
containsString("Philanthropists")));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,7 +478,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
}
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,7 +493,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
|
||||
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost",
|
||||
randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
|
||||
randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE,
|
||||
() -> new SimpleBindRequest("cn=Horatio Hornblower,ou=people,o=sevenSeas", "pass"),
|
||||
() -> "cn=Horatio Hornblower,ou=people,o=sevenSeas");
|
||||
try {
|
||||
assertThat(connectionPool.getCurrentAvailableConnections(),
|
||||
is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_INITIAL_SIZE));
|
||||
|
@ -522,7 +524,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||
|
||||
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost",
|
||||
randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE);
|
||||
randomFrom(ldapServers).getListenPort()), TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE,
|
||||
() -> new SimpleBindRequest("cn=Horatio Hornblower,ou=people,o=sevenSeas", "pass"),
|
||||
() -> "cn=Horatio Hornblower,ou=people,o=sevenSeas");
|
||||
try {
|
||||
assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
|
||||
assertThat(connectionPool.getMaximumAvailableConnections(), is(12));
|
||||
|
@ -547,7 +551,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
||||
} finally {
|
||||
if (searchSessionFactory != null) {
|
||||
searchSessionFactory.shutdown();
|
||||
searchSessionFactory.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -570,7 +574,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
future.get();
|
||||
} finally {
|
||||
if (searchSessionFactory != null) {
|
||||
searchSessionFactory.shutdown();
|
||||
searchSessionFactory.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -619,7 +623,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
||||
} finally {
|
||||
if (searchSessionFactory != null) {
|
||||
searchSessionFactory.shutdown();
|
||||
searchSessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue