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`,
|
the user is expected to match the `sAMAccountName` or `userPrincipalName`,
|
||||||
not the common name.
|
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:
|
To configure an `active_directory` realm:
|
||||||
|
|
||||||
. Add a realm configuration of type `active_directory` to `elasticsearch.yml`
|
. Add a realm configuration of type `active_directory` to `elasticsearch.yml`
|
||||||
|
@ -63,13 +69,10 @@ xpack:
|
||||||
order: 0 <1>
|
order: 0 <1>
|
||||||
domain_name: ad.example.com
|
domain_name: ad.example.com
|
||||||
url: ldaps://ad.example.com:636 <2>
|
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
|
<1> The realm order controls the order in which the configured realms are checked
|
||||||
when authenticating a user.
|
when authenticating a user.
|
||||||
<2> If you don't specify the URL, it defaults to `ldap:<domain_name>:389`.
|
<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
|
IMPORTANT: When you configure realms in `elasticsearch.yml`, only the
|
||||||
realms you specify are used for authentication. If you also want to use 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.
|
. 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
|
===== Multiple Domain Support
|
||||||
When authenticating users across multiple domains in a forest, there are a few minor
|
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`
|
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,
|
assuming an unencrypted connection to port 389. For example,
|
||||||
`ldap://<domain_name>:389`. This settings is required when
|
`ldap://<domain_name>:389`. This settings is required when
|
||||||
connecting using SSL/TLS or via a custom port.
|
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.
|
| `load_balance.type` | no | The behavior to use when there are multiple LDAP URLs defined.
|
||||||
For supported values see <<ad-load-balancing>>.
|
For supported values see <<ad-load-balancing>>.
|
||||||
| `load_balance.cache_ttl` | no | When using `dns_failover` or `dns_round_robin` as the load
|
| `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
|
must be a valid LDAP user search filter, for example
|
||||||
`(&(objectClass=user)(sAMAccountName={0}))`. For more
|
`(&(objectClass=user)(sAMAccountName={0}))`. For more
|
||||||
information, see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx[Search Filter Syntax].
|
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
|
| `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
|
has membership. Defaults to the root of the Active Directory
|
||||||
domain.
|
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
|
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
|
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
|
support this out of the box. The LDAP realm must be configured to run in
|
||||||
_user search_ mode. For more information, see
|
<<ldap-user-search, _user search_ mode>>. The Active Directory realm must be
|
||||||
<<ldap-user-search, Configuring an LDAP Realm with User Search>>.
|
<<ad-settings,configured with a `bind_dn` and `bind_password`>> to support _run as_.
|
||||||
The Active Directory and PKI realms do not 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`
|
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
|
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`::
|
`user_search.pool.enabled`::
|
||||||
Enables or disables connection pooling for user search. When
|
Enables or disables connection pooling for user search. When
|
||||||
disabled a new connection is created for every search. The
|
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`::
|
`user_search.pool.size`::
|
||||||
The maximum number of connections to the LDAP server to allow in the
|
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`::
|
`user_search.pool.initial_size`::
|
||||||
The initial number of connections to create to the LDAP server on startup.
|
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`::
|
`user_search.pool.health_check.enabled`::
|
||||||
Flag to enable or disable a health check on LDAP connections in the connection
|
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`::
|
`user_search.pool.health_check.dn`::
|
||||||
The distinguished name to be retrieved as part of the health check.
|
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
|
Defaults to the value of `bind_dn` if present, and if
|
||||||
specified.
|
not falls back to `user_search.base_dn`.
|
||||||
|
|
||||||
`user_search.pool.health_check.interval`::
|
`user_search.pool.health_check.interval`::
|
||||||
The interval to perform background checks of connections in the pool.
|
The interval to perform background checks of connections in the pool.
|
||||||
Defaults to `60s`.
|
Defaults to `60s`.
|
||||||
|
|
||||||
`group_search.base_dn`::
|
`group_search.base_dn`::
|
||||||
The container DN to search for groups in which the user has membership. When
|
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
|
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
|
`user_search_dn` fields from values in this element if those fields are not
|
||||||
otherwise specified. Required.
|
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`::
|
`unmapped_groups_as_roles`::
|
||||||
Takes a boolean variable. When this element is set to `true`, the names of any
|
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
|
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
|
must be a valid LDAP user search filter, for example
|
||||||
`(&(objectClass=user)(sAMAccountName={0}))`.
|
`(&(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`::
|
`group_search.base_dn`::
|
||||||
The context to search for groups in which the user has membership. Defaults
|
The context to search for groups in which the user has membership. Defaults
|
||||||
to the root of the Active Directory domain.
|
to the root of the Active Directory domain.
|
||||||
|
|
|
@ -24,9 +24,13 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
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.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.search;
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
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 {
|
class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
||||||
|
|
||||||
|
@ -35,11 +39,10 @@ class ActiveDirectoryGroupsResolver implements GroupsResolver {
|
||||||
private final LdapSearchScope scope;
|
private final LdapSearchScope scope;
|
||||||
private final boolean ignoreReferralErrors;
|
private final boolean ignoreReferralErrors;
|
||||||
|
|
||||||
ActiveDirectoryGroupsResolver(Settings settings, String baseDnDefault,
|
ActiveDirectoryGroupsResolver(Settings settings) {
|
||||||
boolean ignoreReferralErrors) {
|
this.baseDn = settings.get("group_search.base_dn", buildDnFromDomain(settings.get(AD_DOMAIN_NAME_SETTING)));
|
||||||
this.baseDn = settings.get("base_dn", baseDnDefault);
|
this.scope = LdapSearchScope.resolve(settings.get("group_search.scope"), LdapSearchScope.SUB_TREE);
|
||||||
this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE);
|
this.ignoreReferralErrors = IGNORE_REFERRAL_ERRORS_SETTING.get(settings);
|
||||||
this.ignoreReferralErrors = ignoreReferralErrors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -8,8 +8,12 @@ package org.elasticsearch.xpack.security.authc.ldap;
|
||||||
import com.unboundid.ldap.sdk.Filter;
|
import com.unboundid.ldap.sdk.Filter;
|
||||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||||
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
|
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
|
||||||
|
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
||||||
import com.unboundid.ldap.sdk.LDAPException;
|
import com.unboundid.ldap.sdk.LDAPException;
|
||||||
|
import com.unboundid.ldap.sdk.LDAPInterface;
|
||||||
import com.unboundid.ldap.sdk.SearchResultEntry;
|
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.logging.log4j.Logger;
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
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.LdapSession.GroupsResolver;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
import org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||||
|
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||||
import org.elasticsearch.xpack.ssl.SSLService;
|
import org.elasticsearch.xpack.ssl.SSLService;
|
||||||
|
|
||||||
import java.util.HashSet;
|
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
|
* 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.
|
* 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";
|
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_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING = "user_search.down_level_filter";
|
||||||
static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope";
|
static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope";
|
||||||
private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})";
|
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 DefaultADAuthenticator defaultADAuthenticator;
|
||||||
final DownLevelADAuthenticator downLevelADAuthenticator;
|
final DownLevelADAuthenticator downLevelADAuthenticator;
|
||||||
final UpnADAuthenticator upnADAuthenticator;
|
final UpnADAuthenticator upnADAuthenticator;
|
||||||
|
|
||||||
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) {
|
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService) throws LDAPException {
|
||||||
super(config, sslService);
|
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();
|
Settings settings = config.settings();
|
||||||
String domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
String domainName = settings.get(AD_DOMAIN_NAME_SETTING);
|
||||||
if (domainName == null) {
|
if (domainName == null) {
|
||||||
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING +
|
throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory");
|
||||||
"] setting for active directory");
|
|
||||||
}
|
}
|
||||||
String domainDN = buildDnFromDomain(domainName);
|
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);
|
metaDataResolver, domainDN);
|
||||||
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
downLevelADAuthenticator = new DownLevelADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||||
metaDataResolver, domainDN, sslService);
|
metaDataResolver, domainDN, sslService);
|
||||||
upnADAuthenticator = new UpnADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
upnADAuthenticator = new UpnADAuthenticator(config, timeout, ignoreReferralErrors, logger, groupResolver,
|
||||||
metaDataResolver, domainDN);
|
metaDataResolver, domainDN);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -88,13 +106,13 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
return new String[] {"ldap://" + settings.get(AD_DOMAIN_NAME_SETTING) + ":389"};
|
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
|
@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
|
// 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
|
// authenticate. If there was a failure pass it back using the listener
|
||||||
Runnable runnable;
|
Runnable runnable;
|
||||||
|
@ -112,6 +130,54 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
runnable.run();
|
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
|
* @param domain active directory domain name
|
||||||
* @return LDAP DN, distinguished name, of the root of the domain
|
* @return LDAP DN, distinguished name, of the root of the domain
|
||||||
|
@ -120,6 +186,14 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
return "DC=" + domain.replace(".", ",DC=");
|
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() {
|
public static Set<Setting<?>> getSettings() {
|
||||||
Set<Setting<?>> settings = new HashSet<>();
|
Set<Setting<?>> settings = new HashSet<>();
|
||||||
settings.addAll(SessionFactory.getSettings());
|
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_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_DOWN_LEVEL_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope));
|
||||||
settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope));
|
settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope));
|
||||||
|
settings.addAll(PoolingSessionFactory.getSettings());
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +229,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
final String userSearchDN;
|
final String userSearchDN;
|
||||||
final LdapSearchScope userSearchScope;
|
final LdapSearchScope userSearchScope;
|
||||||
final String userSearchFilter;
|
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,
|
ADAuthenticator(RealmConfig realm, TimeValue timeout, boolean ignoreReferralErrors, Logger logger,
|
||||||
GroupsResolver groupsResolver, LdapMetaDataResolver metaDataResolver, String domainDN,
|
GroupsResolver groupsResolver, LdapMetaDataResolver metaDataResolver, String domainDN,
|
||||||
|
@ -165,6 +242,8 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
this.groupsResolver = groupsResolver;
|
this.groupsResolver = groupsResolver;
|
||||||
this.metaDataResolver = metaDataResolver;
|
this.metaDataResolver = metaDataResolver;
|
||||||
final Settings settings = realm.settings();
|
final Settings settings = realm.settings();
|
||||||
|
this.bindDN = getBindDN(settings);
|
||||||
|
this.bindPassword = BIND_PASSWORD.get(settings);
|
||||||
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN);
|
||||||
userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE);
|
userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE);
|
||||||
userSearchFilter = settings.get(userSearchFilterSetting, defaultUserSearchFilter);
|
userSearchFilter = settings.get(userSearchFilterSetting, defaultUserSearchFilter);
|
||||||
|
@ -174,7 +253,11 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
ActionListener<LdapSession> listener) {
|
ActionListener<LdapSession> listener) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
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) -> {
|
searchForDN(connection, username, password, Math.toIntExact(timeout.seconds()), ActionListener.wrap((entry) -> {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
IOUtils.close(connection);
|
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) {
|
String bindUsername(String username) {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
@ -209,7 +314,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
return userSearchFilter;
|
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);
|
ActionListener<SearchResultEntry> listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +338,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void searchForDN(LDAPConnection connection, String username, SecureString password,
|
void searchForDN(LDAPInterface connection, String username, SecureString password,
|
||||||
int timeLimitSeconds, ActionListener<SearchResultEntry> listener) {
|
int timeLimitSeconds, ActionListener<SearchResultEntry> listener) {
|
||||||
try {
|
try {
|
||||||
searchForEntry(connection, userSearchDN, userSearchScope.scope(),
|
searchForEntry(connection, userSearchDN, userSearchScope.scope(),
|
||||||
|
@ -276,7 +381,7 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void searchForDN(LDAPConnection connection, String username, SecureString password, int timeLimitSeconds,
|
void searchForDN(LDAPInterface connection, String username, SecureString password, int timeLimitSeconds,
|
||||||
ActionListener<SearchResultEntry> listener) {
|
ActionListener<SearchResultEntry> listener) {
|
||||||
String[] parts = username.split("\\\\");
|
String[] parts = username.split("\\\\");
|
||||||
assert parts.length == 2;
|
assert parts.length == 2;
|
||||||
|
@ -285,7 +390,6 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
|
|
||||||
netBiosDomainNameToDn(connection, netBiosDomainName, username, password, timeLimitSeconds, ActionListener.wrap((domainDN) -> {
|
netBiosDomainNameToDn(connection, netBiosDomainName, username, password, timeLimitSeconds, ActionListener.wrap((domainDN) -> {
|
||||||
if (domainDN == null) {
|
if (domainDN == null) {
|
||||||
IOUtils.close(connection);
|
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -294,22 +398,19 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
accountName), timeLimitSeconds, ignoreReferralErrors,
|
accountName), timeLimitSeconds, ignoreReferralErrors,
|
||||||
listener, attributesToSearchFor(groupsResolver.attributes()));
|
listener, attributesToSearchFor(groupsResolver.attributes()));
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, (e) -> {
|
}, listener::onFailure));
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
|
||||||
listener.onFailure(e);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
int timeLimitSeconds, ActionListener<String> listener) {
|
||||||
final String cachedName = domainNameCache.get(netBiosDomainName);
|
final String cachedName = domainNameCache.get(netBiosDomainName);
|
||||||
|
try {
|
||||||
if (cachedName != null) {
|
if (cachedName != null) {
|
||||||
listener.onResponse(cachedName);
|
listener.onResponse(cachedName);
|
||||||
} else if (usingGlobalCatalog(settings, connection)) {
|
} else if (usingGlobalCatalog(ldapInterface)) {
|
||||||
// the global catalog does not replicate the necessary information to map a netbios
|
// 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
|
// 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
|
// uses the standard ports to avoid adding even more settings and is probably ok as
|
||||||
|
@ -317,54 +418,57 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
final LDAPConnectionOptions options = connectionOptions(config, sslService, logger);
|
||||||
boolean startedSearching = false;
|
boolean startedSearching = false;
|
||||||
LDAPConnection searchConnection = null;
|
LDAPConnection searchConnection = null;
|
||||||
|
LDAPConnection ldapConnection = null;
|
||||||
try {
|
try {
|
||||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
||||||
if (connection.getSSLSession() != null) {
|
if (ldapInterface instanceof LDAPConnection) {
|
||||||
searchConnection = LdapUtils.privilegedConnect(
|
ldapConnection = (LDAPConnection) ldapInterface;
|
||||||
() -> new LDAPConnection(connection.getSocketFactory(), options,
|
|
||||||
connection.getConnectedAddress(), 636));
|
|
||||||
} else {
|
} else {
|
||||||
searchConnection = LdapUtils.privilegedConnect(() ->
|
ldapConnection = LdapUtils.privilegedConnect(((LDAPConnectionPool) ldapInterface)::getConnection);
|
||||||
new LDAPConnection(options, connection.getConnectedAddress(), 389));
|
|
||||||
}
|
}
|
||||||
searchConnection.bind(username, new String(password.getChars()));
|
final LDAPConnection finalLdapConnection = ldapConnection;
|
||||||
|
searchConnection = LdapUtils.privilegedConnect(
|
||||||
|
() -> 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;
|
final LDAPConnection finalConnection = searchConnection;
|
||||||
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
search(finalConnection, domainDN, LdapSearchScope.SUB_TREE.scope(), filter,
|
||||||
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||||
(results) -> {
|
(results) -> {
|
||||||
IOUtils.close(finalConnection);
|
IOUtils.close(finalConnection);
|
||||||
handleSearchResults(results, netBiosDomainName,
|
handleSearchResults(results, netBiosDomainName, domainNameCache, listener);
|
||||||
domainNameCache, listener);
|
|
||||||
}, (e) -> {
|
}, (e) -> {
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
IOUtils.closeWhileHandlingException(finalConnection);
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}),
|
}),
|
||||||
"ncname");
|
"ncname");
|
||||||
startedSearching = true;
|
startedSearching = true;
|
||||||
} catch (LDAPException e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (startedSearching == false) {
|
if (startedSearching == false) {
|
||||||
IOUtils.closeWhileHandlingException(searchConnection);
|
IOUtils.closeWhileHandlingException(searchConnection);
|
||||||
}
|
}
|
||||||
|
if (ldapInterface instanceof LDAPConnectionPool && ldapConnection != null) {
|
||||||
|
((LDAPConnectionPool) ldapInterface).releaseConnection(ldapConnection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
|
||||||
Filter filter = createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName);
|
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(
|
timeLimitSeconds, ignoreReferralErrors, ActionListener.wrap(
|
||||||
(results) -> handleSearchResults(results, netBiosDomainName,
|
(results) -> handleSearchResults(results, netBiosDomainName,
|
||||||
domainNameCache, listener),
|
domainNameCache, listener),
|
||||||
(e) -> {
|
listener::onFailure),
|
||||||
IOUtils.closeWhileHandlingException(connection);
|
|
||||||
listener.onFailure(e);
|
|
||||||
}),
|
|
||||||
"ncname");
|
"ncname");
|
||||||
|
}
|
||||||
} catch (LDAPException e) {
|
} catch (LDAPException e) {
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName,
|
static void handleSearchResults(List<SearchResultEntry> results, String netBiosDomainName,
|
||||||
Cache<String, String> domainNameCache,
|
Cache<String, String> domainNameCache,
|
||||||
|
@ -385,15 +489,32 @@ class ActiveDirectorySessionFactory extends SessionFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean usingGlobalCatalog(Settings settings, LDAPConnection ldapConnection) {
|
static boolean usingGlobalCatalog(LDAPInterface ldap) throws LDAPException {
|
||||||
Boolean usingGlobalCatalog = settings.getAsBoolean("global_catalog", null);
|
if (ldap instanceof LDAPConnection) {
|
||||||
if (usingGlobalCatalog != null) {
|
return usingGlobalCatalog((LDAPConnection) ldap);
|
||||||
return usingGlobalCatalog;
|
} 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;
|
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 class UpnADAuthenticator extends ADAuthenticator {
|
||||||
|
|
||||||
static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))";
|
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);
|
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) {
|
ActionListener<SearchResultEntry> listener) {
|
||||||
String[] parts = username.split("@");
|
String[] parts = username.split("@");
|
||||||
assert parts.length == 2;
|
assert parts.length == 2;
|
||||||
|
|
|
@ -6,25 +6,19 @@
|
||||||
package org.elasticsearch.xpack.security.authc.ldap;
|
package org.elasticsearch.xpack.security.authc.ldap;
|
||||||
|
|
||||||
import com.unboundid.ldap.sdk.Filter;
|
import com.unboundid.ldap.sdk.Filter;
|
||||||
import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck;
|
|
||||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||||
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
import com.unboundid.ldap.sdk.LDAPConnectionPool;
|
||||||
import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
|
|
||||||
import com.unboundid.ldap.sdk.LDAPException;
|
import com.unboundid.ldap.sdk.LDAPException;
|
||||||
import com.unboundid.ldap.sdk.LDAPInterface;
|
import com.unboundid.ldap.sdk.LDAPInterface;
|
||||||
import com.unboundid.ldap.sdk.SearchResultEntry;
|
import com.unboundid.ldap.sdk.SearchResultEntry;
|
||||||
import com.unboundid.ldap.sdk.ServerSet;
|
|
||||||
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
import com.unboundid.ldap.sdk.SimpleBindRequest;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
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.RealmConfig;
|
||||||
import org.elasticsearch.xpack.security.authc.RealmSettings;
|
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.LdapSearchScope;
|
||||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
|
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.LdapSession.GroupsResolver;
|
||||||
|
@ -36,7 +30,6 @@ import org.elasticsearch.xpack.ssl.SSLService;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
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.createFilter;
|
||||||
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry;
|
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;
|
private static final String DEFAULT_USERNAME_ATTRIBUTE = "uid";
|
||||||
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);
|
|
||||||
|
|
||||||
static final String SEARCH_PREFIX = "user_search.";
|
static final String SEARCH_PREFIX = "user_search.";
|
||||||
static final Setting<String> SEARCH_ATTRIBUTE = new Setting<>("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE,
|
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<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,
|
private static final Setting<LdapSearchScope> SEARCH_SCOPE = new Setting<>("user_search.scope", (String) null,
|
||||||
s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope);
|
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<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 final String userSearchBaseDn;
|
private final String userSearchBaseDn;
|
||||||
private final LdapSearchScope scope;
|
private final LdapSearchScope scope;
|
||||||
private final String searchFilter;
|
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 {
|
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();
|
Settings settings = config.settings();
|
||||||
if (SEARCH_BASE_DN.exists(settings)) {
|
if (SEARCH_BASE_DN.exists(settings)) {
|
||||||
userSearchBaseDn = SEARCH_BASE_DN.get(settings);
|
userSearchBaseDn = SEARCH_BASE_DN.get(settings);
|
||||||
|
@ -97,57 +73,10 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
||||||
}
|
}
|
||||||
scope = SEARCH_SCOPE.get(settings);
|
scope = SEARCH_SCOPE.get(settings);
|
||||||
searchFilter = getSearchFilter(config);
|
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=[{}]",
|
logger.info("Realm [{}] is in user-search mode - base_dn=[{}], search filter=[{}]",
|
||||||
config.name(), userSearchBaseDn, searchFilter);
|
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) {
|
static SimpleBindRequest bindRequest(Settings settings) {
|
||||||
if (BIND_DN.exists(settings)) {
|
if (BIND_DN.exists(settings)) {
|
||||||
return new SimpleBindRequest(BIND_DN.get(settings), BIND_PASSWORD.get(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;
|
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
|
* 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) -> {
|
findUser(user, connectionPool, ActionListener.wrap((entry) -> {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
|
@ -204,7 +125,8 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
||||||
* <li>Creates a new LDAPSession with the bound connection</li>
|
* <li>Creates a new LDAPSession with the bound connection</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
private void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) {
|
@Override
|
||||||
|
void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
LDAPConnection connection = null;
|
LDAPConnection connection = null;
|
||||||
try {
|
try {
|
||||||
|
@ -261,33 +183,42 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
LDAPConnection connection = null;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
final LDAPInterface ldapInterface;
|
|
||||||
if (useConnectionPool) {
|
|
||||||
ldapInterface = connectionPool;
|
|
||||||
} else {
|
|
||||||
connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
connection = LdapUtils.privilegedConnect(serverSet::getConnection);
|
||||||
connection.bind(bindRequest(config.settings()));
|
connection.bind(bindRequest(config.settings()));
|
||||||
ldapInterface = connection;
|
final LDAPConnection finalConnection = connection;
|
||||||
}
|
|
||||||
|
|
||||||
findUser(user, ldapInterface, ActionListener.wrap((entry) -> {
|
findUser(user, finalConnection, ActionListener.wrap((entry) -> {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
} else {
|
} else {
|
||||||
boolean sessionCreated = false;
|
boolean sessionCreated = false;
|
||||||
try {
|
try {
|
||||||
final String dn = entry.getDN();
|
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());
|
entry.getAttributes());
|
||||||
sessionCreated = true;
|
sessionCreated = true;
|
||||||
listener.onResponse(session);
|
listener.onResponse(session);
|
||||||
} finally {
|
} finally {
|
||||||
if (sessionCreated == false && useConnectionPool == false) {
|
if (sessionCreated == false) {
|
||||||
IOUtils.close((LDAPConnection) ldapInterface);
|
IOUtils.close(finalConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,16 +247,7 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
||||||
attributesToSearchFor(groupResolver.attributes(), metaDataResolver.attributeNames()));
|
attributesToSearchFor(groupResolver.attributes(), metaDataResolver.attributeNames()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
private static GroupsResolver groupResolver(Settings settings) {
|
||||||
* This method is used to cleanup the connections
|
|
||||||
*/
|
|
||||||
void shutdown() {
|
|
||||||
if (connectionPool != null) {
|
|
||||||
connectionPool.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static GroupsResolver groupResolver(Settings settings) {
|
|
||||||
if (SearchGroupsResolver.BASE_DN.exists(settings)) {
|
if (SearchGroupsResolver.BASE_DN.exists(settings)) {
|
||||||
return new SearchGroupsResolver(settings);
|
return new SearchGroupsResolver(settings);
|
||||||
}
|
}
|
||||||
|
@ -352,17 +274,11 @@ class LdapUserSearchSessionFactory extends SessionFactory {
|
||||||
public static Set<Setting<?>> getSettings() {
|
public static Set<Setting<?>> getSettings() {
|
||||||
Set<Setting<?>> settings = new HashSet<>();
|
Set<Setting<?>> settings = new HashSet<>();
|
||||||
settings.addAll(SessionFactory.getSettings());
|
settings.addAll(SessionFactory.getSettings());
|
||||||
|
settings.addAll(PoolingSessionFactory.getSettings());
|
||||||
settings.add(SEARCH_BASE_DN);
|
settings.add(SEARCH_BASE_DN);
|
||||||
settings.add(SEARCH_SCOPE);
|
settings.add(SEARCH_SCOPE);
|
||||||
settings.add(SEARCH_ATTRIBUTE);
|
settings.add(SEARCH_ATTRIBUTE);
|
||||||
settings.add(POOL_ENABLED);
|
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.add(SEARCH_FILTER);
|
||||||
|
|
||||||
settings.addAll(SearchGroupsResolver.getSettings());
|
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 final class LdapUtils {
|
||||||
|
|
||||||
public static final Filter OBJECT_CLASS_PRESENCE_FILTER =
|
public static final Filter OBJECT_CLASS_PRESENCE_FILTER = Filter.createPresenceFilter("objectClass");
|
||||||
Filter.createPresenceFilter("objectClass");
|
|
||||||
|
|
||||||
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
private static final Logger LOGGER = ESLoggerFactory.getLogger(LdapUtils.class);
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,11 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
||||||
|
|
||||||
public void testResolveSubTree() throws Exception {
|
public void testResolveSubTree() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
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();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, containsInAnyOrder(
|
assertThat(groups, containsInAnyOrder(
|
||||||
|
@ -48,10 +49,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
||||||
public void testResolveOneLevel() throws Exception {
|
public void testResolveOneLevel() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put("scope", LdapSearchScope.ONE_LEVEL)
|
.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();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, hasItem(containsString("Users")));
|
assertThat(groups, hasItem(containsString("Users")));
|
||||||
|
@ -59,11 +60,11 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase {
|
||||||
|
|
||||||
public void testResolveBaseLevel() throws Exception {
|
public void testResolveBaseLevel() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put("scope", LdapSearchScope.BASE)
|
.put("group_search.scope", LdapSearchScope.BASE)
|
||||||
.put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com")
|
.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();
|
.build();
|
||||||
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings,
|
ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings);
|
||||||
"DC=ad,DC=test,DC=elasticsearch,DC=com", false);
|
|
||||||
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
List<String> groups = resolveBlocking(resolver, ldapConnection, BRUCE_BANNER_DN,
|
||||||
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null);
|
||||||
assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin")));
|
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.test.junit.annotations.Network;
|
||||||
import org.elasticsearch.xpack.ssl.VerificationMode;
|
import org.elasticsearch.xpack.ssl.VerificationMode;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
@ -46,8 +47,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
RealmConfig config = new RealmConfig("ad-test",
|
RealmConfig config = new RealmConfig("ad-test",
|
||||||
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false),
|
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false),
|
||||||
globalSettings, new ThreadContext(Settings.EMPTY));
|
globalSettings, new ThreadContext(Settings.EMPTY));
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config,
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
sslService);
|
|
||||||
|
|
||||||
String userName = "ironman";
|
String userName = "ironman";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
|
@ -64,11 +64,13 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Supers")));
|
containsString("Supers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testNetbiosAuth() throws Exception {
|
public void testNetbiosAuth() throws Exception {
|
||||||
final String adUrl = randomFrom("ldap://54.213.145.20:3268", "ldaps://54.213.145.20:3269", AD_LDAP_URL);
|
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));
|
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings,
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String userName = "ades\\ironman";
|
String userName = "ades\\ironman";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
|
@ -85,6 +87,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Supers")));
|
containsString("Supers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849")
|
@AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849")
|
||||||
public void testTcpReadTimeout() throws Exception {
|
public void testTcpReadTimeout() throws Exception {
|
||||||
|
@ -94,33 +97,38 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
.put("ssl.verification_mode", VerificationMode.CERTIFICATE)
|
.put("ssl.verification_mode", VerificationMode.CERTIFICATE)
|
||||||
.put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms")
|
.put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms")
|
||||||
.build();
|
.build();
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
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<>();
|
PlainActionFuture<List<String>> groups = new PlainActionFuture<>();
|
||||||
session(sessionFactory, "ironman", SECURED_PASSWORD).groups(groups);
|
session(sessionFactory, "ironman", SECURED_PASSWORD).groups(groups);
|
||||||
LDAPException expected = expectThrows(LDAPException.class, groups::actionGet);
|
LDAPException expected = expectThrows(LDAPException.class, groups::actionGet);
|
||||||
assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting"));
|
assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testAdAuthAvengers() throws Exception {
|
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));
|
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings,
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
|
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow"};
|
||||||
for (String user : users) {
|
for (String user : users) {
|
||||||
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, user, SECURED_PASSWORD)) {
|
||||||
assertThat("group avenger test for user " + user, groups(ldap), hasItem(containsString("Avengers")));
|
assertThat("group avenger test for user " + user, groups(ldap), hasItem(containsString("Avengers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testAuthenticate() throws Exception {
|
public void testAuthenticate() throws Exception {
|
||||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||||
LdapSearchScope.ONE_LEVEL, false);
|
LdapSearchScope.ONE_LEVEL, false);
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
|
@ -136,13 +144,15 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Supers")));
|
containsString("Supers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testAuthenticateBaseUserSearch() throws Exception {
|
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",
|
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||||
LdapSearchScope.BASE, false);
|
LdapSearchScope.BASE, false);
|
||||||
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),
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
|
@ -158,6 +168,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Supers")));
|
containsString("Supers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testAuthenticateBaseGroupSearch() throws Exception {
|
public void testAuthenticateBaseGroupSearch() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
|
@ -167,8 +178,9 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
"CN=Avengers,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com")
|
"CN=Avengers,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com")
|
||||||
.put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE)
|
.put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE)
|
||||||
.build();
|
.build();
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String userName = "hulk";
|
String userName = "hulk";
|
||||||
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, userName, SECURED_PASSWORD)) {
|
||||||
|
@ -177,13 +189,15 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
assertThat(groups, hasItem(containsString("Avengers")));
|
assertThat(groups, hasItem(containsString("Avengers")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testAuthenticateWithUserPrincipalName() throws Exception {
|
public void testAuthenticateWithUserPrincipalName() throws Exception {
|
||||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||||
LdapSearchScope.ONE_LEVEL, false);
|
LdapSearchScope.ONE_LEVEL, false);
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
//Login with the UserPrincipalName
|
//Login with the UserPrincipalName
|
||||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
|
@ -196,12 +210,14 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Domain Users")));
|
containsString("Domain Users")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testAuthenticateWithSAMAccountName() throws Exception {
|
public void testAuthenticateWithSAMAccountName() throws Exception {
|
||||||
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
|
||||||
LdapSearchScope.ONE_LEVEL, false);
|
LdapSearchScope.ONE_LEVEL, false);
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
//login with sAMAccountName
|
//login with sAMAccountName
|
||||||
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||||
|
@ -215,6 +231,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("Domain Users")));
|
containsString("Domain Users")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testCustomUserFilter() throws Exception {
|
public void testCustomUserFilter() throws Exception {
|
||||||
|
@ -224,8 +241,9 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_FILTER_SETTING,
|
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_FILTER_SETTING,
|
||||||
"(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))")
|
"(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))")
|
||||||
.build();
|
.build();
|
||||||
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
RealmConfig config =
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new RealmConfig("ad-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
//Login with the UserPrincipalName
|
//Login with the UserPrincipalName
|
||||||
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
try (LdapSession ldap = session(sessionFactory, "erik.selvig", SECURED_PASSWORD)) {
|
||||||
|
@ -236,6 +254,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
containsString("CN=Users,CN=Builtin")));
|
containsString("CN=Users,CN=Builtin")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -256,7 +275,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
.put("ssl.truststore.password", "changeit")
|
.put("ssl.truststore.password", "changeit")
|
||||||
.build();
|
.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);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
|
@ -290,7 +310,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
.put("ssl.truststore.password", "changeit")
|
.put("ssl.truststore.password", "changeit")
|
||||||
.build();
|
.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);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
|
@ -318,7 +339,8 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
.put("ssl.truststore.password", "changeit")
|
.put("ssl.truststore.password", "changeit")
|
||||||
.build();
|
.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);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
String user = "Bruce Banner";
|
||||||
|
@ -334,8 +356,9 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAdAuthWithHostnameVerification() throws Exception {
|
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));
|
RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings,
|
||||||
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService);
|
new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService)) {
|
||||||
|
|
||||||
String userName = "ironman";
|
String userName = "ironman";
|
||||||
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
UncategorizedExecutionException e = expectThrows(UncategorizedExecutionException.class,
|
||||||
|
@ -343,7 +366,9 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
assertThat(e.getCause(), instanceOf(ExecutionException.class));
|
||||||
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
assertThat(e.getCause().getCause(), instanceOf(LDAPException.class));
|
||||||
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
final LDAPException expected = (LDAPException) e.getCause().getCause();
|
||||||
assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated")));
|
assertThat(expected.getMessage(),
|
||||||
|
anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testStandardLdapHostnameVerification() throws Exception {
|
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(LdapTestCase.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
|
||||||
.put("ssl.verification_mode", VerificationMode.FULL)
|
.put("ssl.verification_mode", VerificationMode.FULL)
|
||||||
.build();
|
.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);
|
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService);
|
||||||
|
|
||||||
String user = "Bruce Banner";
|
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")));
|
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()
|
Settings.Builder builder = Settings.builder()
|
||||||
.put(ActiveDirectorySessionFactory.URLS_SETTING, ldapUrl)
|
.put(ActiveDirectorySessionFactory.URLS_SETTING, ldapUrl)
|
||||||
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, adDomainName);
|
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, adDomainName);
|
||||||
|
@ -374,10 +423,23 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
} else {
|
} else {
|
||||||
builder.put(ActiveDirectorySessionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification);
|
builder.put(ActiveDirectorySessionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useGlobalSSL == false) {
|
if (useGlobalSSL == false) {
|
||||||
builder.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
|
builder.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
|
||||||
.put("ssl.truststore.password", "changeit");
|
.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();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,6 +449,12 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI
|
||||||
return future.actionGet();
|
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) {
|
private List<String> groups(LdapSession ldapSession) {
|
||||||
PlainActionFuture<List<String>> future = new PlainActionFuture<>();
|
PlainActionFuture<List<String>> future = new PlainActionFuture<>();
|
||||||
ldapSession.groups(future);
|
ldapSession.groups(future);
|
||||||
|
|
|
@ -230,7 +230,7 @@ public class LdapRealmTests extends LdapTestCase {
|
||||||
try {
|
try {
|
||||||
assertThat(sessionFactory, is(instanceOf(LdapUserSearchSessionFactory.class)));
|
assertThat(sessionFactory, is(instanceOf(LdapUserSearchSessionFactory.class)));
|
||||||
} finally {
|
} finally {
|
||||||
((LdapUserSearchSessionFactory)sessionFactory).shutdown();
|
((LdapUserSearchSessionFactory)sessionFactory).close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
try {
|
try {
|
||||||
assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true));
|
assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true));
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -140,7 +140,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertThat(dn, containsString(user));
|
assertThat(dn, containsString(user));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -177,7 +177,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertNull(session(sessionFactory, user, userPass));
|
assertNull(session(sessionFactory, user, userPass));
|
||||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -223,7 +223,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertThat(dn, containsString(user));
|
assertThat(dn, containsString(user));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -260,7 +260,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertNull(session(sessionFactory, user, userPass));
|
assertNull(session(sessionFactory, user, userPass));
|
||||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -306,7 +306,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertThat(dn, containsString(user));
|
assertThat(dn, containsString(user));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -342,7 +342,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertNull(session(sessionFactory, user, userPass));
|
assertNull(session(sessionFactory, user, userPass));
|
||||||
assertNull(unauthenticatedSession(sessionFactory, user));
|
assertNull(unauthenticatedSession(sessionFactory, user));
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useAttribute) {
|
if (useAttribute) {
|
||||||
|
@ -380,7 +380,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
assertThat(dn, containsString("William Bush"));
|
assertThat(dn, containsString("William Bush"));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
containsString("Philanthropists")));
|
containsString("Philanthropists")));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sessionFactory.shutdown();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +493,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
|
||||||
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost",
|
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 {
|
try {
|
||||||
assertThat(connectionPool.getCurrentAvailableConnections(),
|
assertThat(connectionPool.getCurrentAvailableConnections(),
|
||||||
is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_INITIAL_SIZE));
|
is(LdapUserSearchSessionFactory.DEFAULT_CONNECTION_POOL_INITIAL_SIZE));
|
||||||
|
@ -522,7 +524,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
.build(), globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
|
||||||
|
|
||||||
LDAPConnectionPool connectionPool = LdapUserSearchSessionFactory.createConnectionPool(config, new SingleServerSet("localhost",
|
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 {
|
try {
|
||||||
assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
|
assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
|
||||||
assertThat(connectionPool.getMaximumAvailableConnections(), is(12));
|
assertThat(connectionPool.getMaximumAvailableConnections(), is(12));
|
||||||
|
@ -547,7 +551,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
||||||
} finally {
|
} finally {
|
||||||
if (searchSessionFactory != null) {
|
if (searchSessionFactory != null) {
|
||||||
searchSessionFactory.shutdown();
|
searchSessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,7 +574,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
future.get();
|
future.get();
|
||||||
} finally {
|
} finally {
|
||||||
if (searchSessionFactory != null) {
|
if (searchSessionFactory != null) {
|
||||||
searchSessionFactory.shutdown();
|
searchSessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,7 +623,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
searchSessionFactory = new LdapUserSearchSessionFactory(config, sslService);
|
||||||
} finally {
|
} finally {
|
||||||
if (searchSessionFactory != null) {
|
if (searchSessionFactory != null) {
|
||||||
searchSessionFactory.shutdown();
|
searchSessionFactory.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue