Add secure_bind_password to LDAP realm (elastic/x-pack-elasticsearch#4192)

Adds a SecureSetting option for the "bind_password" in LDAP/AD realms
and deprecates the non-secure version.

LDAP bind passwords should now be configured with the setting
`xpack.security.authc.realms.REALM_NAME.secure_bind_password`
in the elasticsearch keystore.

Original commit: elastic/x-pack-elasticsearch@1a0cebd77e
This commit is contained in:
Tim Vernum 2018-03-29 16:31:45 +10:00 committed by GitHub
parent 01ab4782a1
commit e69c5d4d48
15 changed files with 320 additions and 183 deletions

View File

@ -93,7 +93,7 @@ connections to Active Directory. These pooled connection reduce the number of
resources that must be created and destroyed with every user authentication. 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 The following example shows the configuration of a bind user through the user of the
`bind_dn` and `bind_password` settings. `bind_dn` and `secure_bind_password` settings.
[source, yaml] [source, yaml]
------------------------------------------------------------ ------------------------------------------------------------
@ -107,12 +107,20 @@ xpack:
domain_name: ad.example.com domain_name: ad.example.com
url: ldaps://ad.example.com:636 url: ldaps://ad.example.com:636
bind_dn: es_svc_user@ad.example.com <1> 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. <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 Without a bind user configured, all requests run as the user that is authenticating
with Elasticsearch. with Elasticsearch.
The password for the `bind_dn` user should be configured by adding the appropriate
`secure_bind_password` setting to the {es} keystore.
For example, the following command adds the password for the example realm above:
[source, shell]
------------------------------------------------------------
bin/elasticsearch-keystore add xpack.security.authc.realms.active_directory.secure_bind_password
------------------------------------------------------------
When a bind user is configured, connection pooling is enabled by default. When a bind user is configured, connection pooling is enabled by default.
Connection pooling can be disabled using the `user_search.pool.enabled` setting. Connection pooling can be disabled using the `user_search.pool.enabled` setting.
@ -223,6 +231,9 @@ operation are supported: failover and load balancing
Active Directory. Due to its potential security impact, Active Directory. Due to its potential security impact,
`bind_password` is not exposed via the `bind_password` is not exposed via the
{ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API]. {ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API].
*Deprecated.* Use `secure_bind_password` instead.
| `secure_bind_password` | no | ({ref}/secure-settings.html[Secure])
The password for the user that is used to bind to Active Directory.
| `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

View File

@ -61,7 +61,6 @@ xpack:
order: 0 order: 0
url: "ldaps://ldap.example.com:636" url: "ldaps://ldap.example.com:636"
bind_dn: "cn=ldapuser, ou=users, o=services, dc=example, dc=com" bind_dn: "cn=ldapuser, ou=users, o=services, dc=example, dc=com"
bind_password: x-pack-test-password
user_search: user_search:
base_dn: "dc=example,dc=com" base_dn: "dc=example,dc=com"
attribute: cn attribute: cn
@ -72,6 +71,15 @@ xpack:
unmapped_groups_as_roles: false unmapped_groups_as_roles: false
------------------------------------------------------------ ------------------------------------------------------------
+ +
The password for the `bind_dn` user should be configured by adding the appropriate
`secure_bind_password` setting to the {es} keystore.
For example, the following command adds the password for the example realm above:
+
[source, shell]
------------------------------------------------------------
bin/elasticsearch-keystore add xpack.security.authc.realms.ldap1.secure_bind_password
------------------------------------------------------------
+
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
`native` or `file` realms, you must include them in the realm chain. `native` or `file` realms, you must include them in the realm chain.
@ -281,9 +289,12 @@ failover and load balancing modes of operation.
impact, `bind_dn` is not exposed via the impact, `bind_dn` is not exposed via the
{ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API]. {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 the | `bind_password` | no | The password for the user that is used to bind to the
LDAP. Due to its potential security impact, LDAP directory. Due to its potential security impact,
`bind_password` is not exposed via the `bind_password` is not exposed via the
{ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API]. {ref}/cluster-nodes-info.html#cluster-nodes-info[nodes info API].
*Deprecated.* Use `secure_bind_password` instead.
| `secure_bind_password` | no | ({ref}/secure-settings.html[Secure])
The password for the user that is used to bind to LDAP directory.
| `user_search.base_dn` | yes | Specifies a container DN to search for users. | `user_search.base_dn` | yes | Specifies a container DN to search for users.
| `user_search.scope` | no | The scope of the user search. Valid values are `sub_tree`, | `user_search.scope` | no | The scope of the user search. Valid values are `sub_tree`,
`one_level` or `base`. `one_level` only searches objects `one_level` or `base`. `one_level` only searches objects

View File

@ -10,8 +10,8 @@ To "run as" (impersonate) another user, you must be able to retrieve the user fr
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 must be configured to run in support this out of the box. The LDAP realm must be configured to run in
<<ldap-user-search, _user search_ mode>>. The Active Directory realm must be <<ldap-user-search, _user search_ mode>>. The Active Directory realm must be
<<ad-settings,configured with a `bind_dn` and `bind_password`>> to support _run as_. <<ad-settings,configured with a `bind_dn` and `secure_bind_password`>> to support
The PKI realm does not support _run as_. _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

View File

@ -182,7 +182,12 @@ If this is not specified, an anonymous bind will be attempted.
Defaults to Empty. Defaults to Empty.
`bind_password`:: `bind_password`::
The password for the user that will be used to bind to the LDAP. The password for the user that will be used to bind to the LDAP directory.
Defaults to Empty.
*Deprecated.* Use `secure_bind_password` instead.
`secure_bind_password` (<<secure-settings,Secure>>)::
The password for the user that will be used to bind to the LDAP directory.
Defaults to Empty. Defaults to Empty.
`user_dn_templates`:: `user_dn_templates`::
@ -410,6 +415,11 @@ Defaults to Empty.
`bind_password`:: `bind_password`::
The password for the user that will be used to bind to Active Directory. The password for the user that will be used to bind to Active Directory.
Defaults to Empty. Defaults to Empty.
*Deprecated.* Use `secure_bind_password` instead.
`secure_bind_password` (<<secure-settings,Secure>>)::
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

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.security.authc.ldap; package org.elasticsearch.xpack.core.security.authc.ldap;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
@ -12,11 +13,15 @@ import org.elasticsearch.common.util.set.Sets;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import static org.elasticsearch.common.settings.SecureSetting.secureString;
public final class PoolingSessionFactorySettings { public final class PoolingSessionFactorySettings {
public static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L); public static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L);
public static final Setting<String> BIND_DN = Setting.simpleString("bind_dn", Setting.Property.NodeScope, Setting.Property.Filtered); public static final Setting<String> BIND_DN = Setting.simpleString("bind_dn", Setting.Property.NodeScope, Setting.Property.Filtered);
public static final Setting<String> BIND_PASSWORD = Setting.simpleString("bind_password", Setting.Property.NodeScope, public static final Setting<SecureString> LEGACY_BIND_PASSWORD = new Setting<>("bind_password", "", SecureString::new,
Setting.Property.Filtered); Setting.Property.NodeScope, Setting.Property.Filtered, Setting.Property.Deprecated);
public static final Setting<SecureString> SECURE_BIND_PASSWORD = secureString("secure_bind_password", LEGACY_BIND_PASSWORD);
public static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; public static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0;
public static final Setting<Integer> POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size", public static final Setting<Integer> POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size",
DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope); DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope);
@ -34,6 +39,6 @@ public final class PoolingSessionFactorySettings {
public static Set<Setting<?>> getSettings() { public static Set<Setting<?>> getSettings() {
return Sets.newHashSet(POOL_INITIAL_SIZE, POOL_SIZE, HEALTH_CHECK_ENABLED, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_DN, BIND_DN, return Sets.newHashSet(POOL_INITIAL_SIZE, POOL_SIZE, HEALTH_CHECK_ENABLED, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_DN, BIND_DN,
BIND_PASSWORD); SECURE_BIND_PASSWORD, LEGACY_BIND_PASSWORD);
} }
} }

View File

@ -14,7 +14,6 @@ import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldap.sdk.SimpleBindRequest;
import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.action.ActionRunnable;
@ -25,6 +24,7 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.RealmSettings;
@ -66,14 +66,9 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException { ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException {
super(config, sslService, new ActiveDirectoryGroupsResolver(config.settings()), super(config, sslService, new ActiveDirectoryGroupsResolver(config.settings()),
ActiveDirectorySessionFactorySettings.POOL_ENABLED, () -> { ActiveDirectorySessionFactorySettings.POOL_ENABLED,
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) { PoolingSessionFactorySettings.BIND_DN.exists(config.settings())? getBindDN(config.settings()) : null,
return new SimpleBindRequest(getBindDN(config.settings()), () -> {
PoolingSessionFactorySettings.BIND_PASSWORD.get(config.settings()));
} else {
return new SimpleBindRequest();
}
}, () -> {
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) { if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) {
final String healthCheckDn = PoolingSessionFactorySettings.BIND_DN.get(config.settings()); final String healthCheckDn = PoolingSessionFactorySettings.BIND_DN.get(config.settings());
if (healthCheckDn.isEmpty() && healthCheckDn.indexOf('=') > 0) { if (healthCheckDn.isEmpty() && healthCheckDn.indexOf('=') > 0) {
@ -155,9 +150,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
} }
try { try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection); final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = new SimpleBindRequest(getBindDN(config.settings()), LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
PoolingSessionFactorySettings.BIND_PASSWORD.get(config.settings()));
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
@Override @Override
public void onFailure(Exception e) { public void onFailure(Exception e) {
@ -225,7 +218,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
final LdapSearchScope userSearchScope; final LdapSearchScope userSearchScope;
final String userSearchFilter; final String userSearchFilter;
final String bindDN; final String bindDN;
final String bindPassword; // TODO this needs to be a setting in the secure settings store! final SecureString bindPassword;
final ThreadPool threadPool; final ThreadPool threadPool;
ADAuthenticator(RealmConfig realm, TimeValue timeout, boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver, ADAuthenticator(RealmConfig realm, TimeValue timeout, boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver,
@ -239,7 +232,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
this.metaDataResolver = metaDataResolver; this.metaDataResolver = metaDataResolver;
final Settings settings = realm.settings(); final Settings settings = realm.settings();
this.bindDN = getBindDN(settings); this.bindDN = getBindDN(settings);
this.bindPassword = PoolingSessionFactorySettings.BIND_PASSWORD.get(settings); this.bindPassword = PoolingSessionFactorySettings.SECURE_BIND_PASSWORD.get(settings);
this.threadPool = threadPool; this.threadPool = threadPool;
userSearchDN = settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_BASEDN_SETTING, domainDN); userSearchDN = settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_BASEDN_SETTING, domainDN);
userSearchScope = LdapSearchScope.resolve(settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_SCOPE_SETTING), userSearchScope = LdapSearchScope.resolve(settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_SCOPE_SETTING),
@ -274,7 +267,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
if (bindDN.isEmpty()) { if (bindDN.isEmpty()) {
searchRunnable.run(); searchRunnable.run();
} else { } else {
final SimpleBindRequest bind = new SimpleBindRequest(bindDN, bindPassword); final SimpleBindRequest bind = new SimpleBindRequest(bindDN, CharArrays.toUtf8Bytes(bindPassword.getChars()));
LdapUtils.maybeForkThenBind(connection, bind, threadPool, searchRunnable); LdapUtils.maybeForkThenBind(connection, bind, threadPool, searchRunnable);
} }
} }
@ -439,7 +432,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars()); final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars());
final SimpleBindRequest bind = bindDN.isEmpty() final SimpleBindRequest bind = bindDN.isEmpty()
? new SimpleBindRequest(username, passwordBytes) ? new SimpleBindRequest(username, passwordBytes)
: new SimpleBindRequest(bindDN, bindPassword); : new SimpleBindRequest(bindDN, CharArrays.toUtf8Bytes(bindPassword.getChars()));
LdapUtils.maybeForkThenBind(searchConnection, bind, threadPool, new ActionRunnable<String>(listener) { LdapUtils.maybeForkThenBind(searchConnection, bind, threadPool, new ActionRunnable<String>(listener) {
@Override @Override
protected void doRun() throws Exception { protected void doRun() throws Exception {

View File

@ -12,17 +12,16 @@ 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.SimpleBindRequest; import com.unboundid.ldap.sdk.SimpleBindRequest;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.action.ActionRunnable;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings; import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.xpack.core.security.authc.support.CharArrays; import org.elasticsearch.xpack.core.security.authc.support.CharArrays;
@ -31,6 +30,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 static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_DN;
import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor;
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;
@ -45,10 +45,10 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
LdapUserSearchSessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException { LdapUserSearchSessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException {
super(config, sslService, groupResolver(config.settings()), LdapUserSearchSessionFactorySettings.POOL_ENABLED, super(config, sslService, groupResolver(config.settings()), LdapUserSearchSessionFactorySettings.POOL_ENABLED,
() -> LdapUserSearchSessionFactory.bindRequest(config.settings()), BIND_DN.exists(config.settings()) ? BIND_DN.get(config.settings()) : null,
() -> { () -> {
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) { if (BIND_DN.exists(config.settings())) {
return PoolingSessionFactorySettings.BIND_DN.get(config.settings()); return BIND_DN.get(config.settings());
} else { } else {
return LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN.get(config.settings()); return LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN.get(config.settings());
} }
@ -66,15 +66,6 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
config.name(), userSearchBaseDn, searchFilter); config.name(), userSearchBaseDn, searchFilter);
} }
static SimpleBindRequest bindRequest(Settings settings) {
if (PoolingSessionFactorySettings.BIND_DN.exists(settings)) {
return new SimpleBindRequest(PoolingSessionFactorySettings.BIND_DN.get(settings),
PoolingSessionFactorySettings.BIND_PASSWORD.get(settings));
} else {
return new SimpleBindRequest();
}
}
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;
} }
@ -118,8 +109,7 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) { void getSessionWithoutPool(String user, SecureString password, ActionListener<LdapSession> listener) {
try { try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection); final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = bindRequest(config.settings()); LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
@Override @Override
protected void doRun() throws Exception { protected void doRun() throws Exception {
findUser(user, connection, ActionListener.wrap((entry) -> { findUser(user, connection, ActionListener.wrap((entry) -> {
@ -133,7 +123,7 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
LdapUtils.maybeForkThenBind(connection, userBind, threadPool, new AbstractRunnable() { LdapUtils.maybeForkThenBind(connection, userBind, threadPool, new AbstractRunnable() {
@Override @Override
protected void doRun() throws Exception { protected void doRun() throws Exception {
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() { LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
@Override @Override
protected void doRun() throws Exception { protected void doRun() throws Exception {
@ -195,8 +185,7 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener) { void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener) {
try { try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection); final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = bindRequest(config.settings()); LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
@Override @Override
protected void doRun() throws Exception { protected void doRun() throws Exception {
findUser(user, connection, ActionListener.wrap((entry) -> { findUser(user, connection, ActionListener.wrap((entry) -> {

View File

@ -11,9 +11,11 @@ import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.ldap.sdk.ServerSet;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.lease.Releasable;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
@ -23,6 +25,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.support.CharArrays;
import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapMetaDataResolver; 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.LdapSession;
@ -31,6 +34,9 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD;
import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.SECURE_BIND_PASSWORD;
/** /**
* Base class for LDAP session factories that can make use of a connection pool * Base class for LDAP session factories that can make use of a connection pool
*/ */
@ -39,6 +45,7 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl
private final boolean useConnectionPool; private final boolean useConnectionPool;
private final LDAPConnectionPool connectionPool; private final LDAPConnectionPool connectionPool;
final SimpleBindRequest bindCredentials;
final LdapMetaDataResolver metaDataResolver; final LdapMetaDataResolver metaDataResolver;
final LdapSession.GroupsResolver groupResolver; final LdapSession.GroupsResolver groupResolver;
@ -48,19 +55,41 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl
* @param sslService the ssl service to get a socket factory or context from * @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 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 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 bindDn the DN of the user to be used for pooled connections (or null to perform anonymous bind)
* @param healthCheckDNSupplier a supplier for the dn to query for health checks * @param healthCheckDNSupplier a supplier for the dn to query for health checks
* @param threadPool a thread pool used for async queries execution * @param threadPool a thread pool used for async queries execution
*/ */
PoolingSessionFactory(RealmConfig config, SSLService sslService, LdapSession.GroupsResolver groupResolver, PoolingSessionFactory(RealmConfig config, SSLService sslService, LdapSession.GroupsResolver groupResolver,
Setting<Boolean> poolingEnabled, Supplier<BindRequest> bindRequestSupplier, Supplier<String> healthCheckDNSupplier, Setting<Boolean> poolingEnabled, @Nullable String bindDn, Supplier<String> healthCheckDNSupplier,
ThreadPool threadPool) throws LDAPException { ThreadPool threadPool) throws LDAPException {
super(config, sslService, threadPool); super(config, sslService, threadPool);
this.groupResolver = groupResolver; this.groupResolver = groupResolver;
this.metaDataResolver = new LdapMetaDataResolver(config.settings(), ignoreReferralErrors); this.metaDataResolver = new LdapMetaDataResolver(config.settings(), ignoreReferralErrors);
final byte[] bindPassword;
if (LEGACY_BIND_PASSWORD.exists(config.settings())) {
if (SECURE_BIND_PASSWORD.exists(config.settings())) {
throw new IllegalArgumentException("You cannot specify both ["
+ RealmSettings.getFullSettingKey(config, LEGACY_BIND_PASSWORD) + "] and ["
+ RealmSettings.getFullSettingKey(config, SECURE_BIND_PASSWORD) + "]");
} else {
bindPassword = CharArrays.toUtf8Bytes(LEGACY_BIND_PASSWORD.get(config.settings()).getChars());
}
} else if (SECURE_BIND_PASSWORD.exists(config.settings())) {
bindPassword = CharArrays.toUtf8Bytes(SECURE_BIND_PASSWORD.get(config.settings()).getChars());
} else {
bindPassword = null;
}
if (bindDn == null) {
bindCredentials = new SimpleBindRequest();
} else {
bindCredentials = new SimpleBindRequest(bindDn, bindPassword);
}
this.useConnectionPool = poolingEnabled.get(config.settings()); this.useConnectionPool = poolingEnabled.get(config.settings());
if (useConnectionPool) { if (useConnectionPool) {
this.connectionPool = createConnectionPool(config, serverSet, timeout, logger, bindRequestSupplier, healthCheckDNSupplier); this.connectionPool = createConnectionPool(config, serverSet, timeout, logger, bindCredentials, healthCheckDNSupplier);
} else { } else {
this.connectionPool = null; this.connectionPool = null;
} }
@ -111,10 +140,9 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl
* Creates the connection pool that will be used by the session factory and initializes the health check support * 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, static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, Logger logger,
Supplier<BindRequest> bindRequestSupplier, BindRequest bindRequest,
Supplier<String> healthCheckDnSupplier) throws LDAPException { Supplier<String> healthCheckDnSupplier) throws LDAPException {
Settings settings = config.settings(); Settings settings = config.settings();
BindRequest bindRequest = bindRequestSupplier.get();
final int initialSize = PoolingSessionFactorySettings.POOL_INITIAL_SIZE.get(settings); final int initialSize = PoolingSessionFactorySettings.POOL_INITIAL_SIZE.get(settings);
final int size = PoolingSessionFactorySettings.POOL_SIZE.get(settings); final int size = PoolingSessionFactorySettings.POOL_SIZE.get(settings);
LDAPConnectionPool pool = null; LDAPConnectionPool pool = null;

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.test; package org.elasticsearch.test;
import org.bouncycastle.operator.OperatorCreationException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Guice; import org.elasticsearch.common.inject.Guice;
import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.inject.Injector;
@ -14,17 +13,13 @@ import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.settings.SettingsModule; import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
import org.elasticsearch.xpack.security.LocalStateSecurity; import org.elasticsearch.xpack.security.LocalStateSecurity;
import org.elasticsearch.xpack.security.Security;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.DestroyFailedException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -40,6 +35,8 @@ public class SettingsFilterTests extends ESTestCase {
private MockSecureSettings mockSecureSettings = new MockSecureSettings(); private MockSecureSettings mockSecureSettings = new MockSecureSettings();
public void testFiltering() throws Exception { public void testFiltering() throws Exception {
final boolean useLegacyLdapBindPassword = randomBoolean();
configureUnfilteredSetting("xpack.security.authc.realms.file.type", "file"); configureUnfilteredSetting("xpack.security.authc.realms.file.type", "file");
// ldap realm filtering // ldap realm filtering
@ -48,7 +45,11 @@ public class SettingsFilterTests extends ESTestCase {
configureUnfilteredSetting("xpack.security.authc.realms.ldap1.url", "ldap://host.domain"); configureUnfilteredSetting("xpack.security.authc.realms.ldap1.url", "ldap://host.domain");
configureFilteredSetting("xpack.security.authc.realms.ldap1.hostname_verification", Boolean.toString(randomBoolean())); configureFilteredSetting("xpack.security.authc.realms.ldap1.hostname_verification", Boolean.toString(randomBoolean()));
configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_dn", randomAlphaOfLength(5)); configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_dn", randomAlphaOfLength(5));
if (useLegacyLdapBindPassword) {
configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_password", randomAlphaOfLength(5)); configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_password", randomAlphaOfLength(5));
} else {
configureSecureSetting("xpack.security.authc.realms.ldap1.secure_bind_password", randomAlphaOfLengthBetween(3, 8));
}
// active directory filtering // active directory filtering
configureUnfilteredSetting("xpack.security.authc.realms.ad1.type", "active_directory"); configureUnfilteredSetting("xpack.security.authc.realms.ad1.type", "active_directory");
@ -93,7 +94,7 @@ public class SettingsFilterTests extends ESTestCase {
// custom settings, potentially added by a plugin // custom settings, potentially added by a plugin
configureFilteredSetting("foo.bar", "_secret"); configureFilteredSetting("foo.bar", "_secret");
configureFilteredSetting("foo.baz", "_secret");; configureFilteredSetting("foo.baz", "_secret");
configureFilteredSetting("bar.baz", "_secret"); configureFilteredSetting("bar.baz", "_secret");
configureUnfilteredSetting("baz.foo", "_not_a_secret"); configureUnfilteredSetting("baz.foo", "_not_a_secret");
configureFilteredSetting("xpack.security.hide_settings", "foo.*,bar.baz"); configureFilteredSetting("xpack.security.hide_settings", "foo.*,bar.baz");
@ -124,6 +125,10 @@ public class SettingsFilterTests extends ESTestCase {
for (Map.Entry<String, Matcher> entry : settingsMatcherMap.entrySet()) { for (Map.Entry<String, Matcher> entry : settingsMatcherMap.entrySet()) {
assertThat(filteredSettings.get(entry.getKey()), entry.getValue()); assertThat(filteredSettings.get(entry.getKey()), entry.getValue());
} }
if (useLegacyLdapBindPassword) {
assertSettingDeprecationsAndWarnings(new Setting<?>[]{PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD});
}
} }
private void configureUnfilteredSetting(String settingName, String value) { private void configureUnfilteredSetting(String settingName, String value) {

View File

@ -12,9 +12,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -138,9 +138,12 @@ public class RealmSettingsTests extends ESTestCase {
private Settings.Builder ldapSettings(boolean userSearch, boolean groupSearch) { private Settings.Builder ldapSettings(boolean userSearch, boolean groupSearch) {
final Settings.Builder builder = commonLdapSettings("ldap", true) final Settings.Builder builder = commonLdapSettings("ldap", true)
.put("bind_dn", "elasticsearch") .put("bind_dn", "elasticsearch")
.put("bind_password", "t0p_s3cr3t")
.put("follow_referrals", randomBoolean()); .put("follow_referrals", randomBoolean());
SecuritySettingsSource.addSecureSettings(builder, secureSettings -> {
secureSettings.setString("bind_password", "t0p_s3cr3t");
});
if (userSearch) { if (userSearch) {
builder.put("user_search.base_dn", "o=people, dc=example, dc=com"); builder.put("user_search.base_dn", "o=people, dc=example, dc=com");
builder.put("user_search.scope", "sub_tree"); builder.put("user_search.scope", "sub_tree");

View File

@ -14,7 +14,9 @@ import com.unboundid.ldap.sdk.schema.Schema;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
@ -63,12 +65,12 @@ import static org.mockito.Mockito.verify;
/** /**
* Active Directory Realm tests that use the UnboundID In Memory Directory Server * Active Directory Realm tests that use the UnboundID In Memory Directory Server
* * <p>
* AD is not LDAPv3 compliant so a workaround is needed * AD is not LDAPv3 compliant so a workaround is needed
* AD realm binds with userPrincipalName but this is not a valid DN, so we have to add a second userPrincipalName to the * AD realm binds with userPrincipalName but this is not a valid DN, so we have to add a second userPrincipalName to the
* users in the ldif in the form of CN=user@domain.com or a set the sAMAccountName to CN=user when testing authentication * users in the ldif in the form of CN=user@domain.com or a set the sAMAccountName to CN=user when testing authentication
* with the sAMAccountName field. * with the sAMAccountName field.
* * <p>
* The username used to authenticate then has to be in the form of CN=user. Finally the username needs to be added as an * The username used to authenticate then has to be in the form of CN=user. Finally the username needs to be added as an
* additional bind DN with a password in the test setup since it really is not a DN in the ldif file * additional bind DN with a password in the test setup since it really is not a DN in the ldif file
*/ */
@ -248,11 +250,18 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
} }
private void doUnauthenticatedLookup(boolean pooled) throws Exception { private void doUnauthenticatedLookup(boolean pooled) throws Exception {
Settings settings = settings(Settings.builder() final Settings.Builder builder = Settings.builder()
.put(ActiveDirectorySessionFactorySettings.POOL_ENABLED.getKey(), pooled) .put(ActiveDirectorySessionFactorySettings.POOL_ENABLED.getKey(), pooled)
.put(PoolingSessionFactorySettings.BIND_DN.getKey(), "CN=ironman@ad.test.elasticsearch.com") .put(PoolingSessionFactorySettings.BIND_DN.getKey(), "CN=ironman@ad.test.elasticsearch.com");
.put(PoolingSessionFactorySettings.BIND_PASSWORD.getKey(), PASSWORD) final boolean useLegacyBindPassword = randomBoolean();
.build()); if (useLegacyBindPassword) {
builder.put(PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD.getKey(), PASSWORD);
} else {
final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(PoolingSessionFactorySettings.SECURE_BIND_PASSWORD.getKey(), PASSWORD);
builder.setSecureSettings(secureSettings);
}
Settings settings = settings(builder.build());
RealmConfig config = new RealmConfig("testUnauthenticatedLookupWithConnectionPool", settings, globalSettings, RealmConfig config = new RealmConfig("testUnauthenticatedLookupWithConnectionPool", settings, globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService, threadPool)) { try (ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService, threadPool)) {

View File

@ -8,6 +8,8 @@ package org.elasticsearch.xpack.security.authc.ldap;
import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.LDAPURL;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureSettings;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -229,7 +231,7 @@ public class LdapRealmTests extends LdapTestCase {
.putList(URLS_SETTING, ldapUrls()) .putList(URLS_SETTING, ldapUrls())
.put("user_search.base_dn", "") .put("user_search.base_dn", "")
.put("bind_dn", "cn=Thomas Masterman Hardy,ou=people,o=sevenSeas") .put("bind_dn", "cn=Thomas Masterman Hardy,ou=people,o=sevenSeas")
.put("bind_password", PASSWORD) .setSecureSettings(secureSettings("secure_bind_password", PASSWORD))
.put("group_search.base_dn", groupSearchBase) .put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", LdapSearchScope.SUB_TREE) .put("group_search.scope", LdapSearchScope.SUB_TREE)
.put("ssl.verification_mode", VerificationMode.CERTIFICATE) .put("ssl.verification_mode", VerificationMode.CERTIFICATE)
@ -243,6 +245,12 @@ public class LdapRealmTests extends LdapTestCase {
} }
} }
private MockSecureSettings secureSettings(String key, String value) {
final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(key, value);
return secureSettings;
}
public void testLdapRealmThrowsExceptionForUserTemplateAndSearchSettings() throws Exception { public void testLdapRealmThrowsExceptionForUserTemplateAndSearchSettings() throws Exception {
Settings settings = Settings.builder() Settings settings = Settings.builder()
.putList(URLS_SETTING, ldapUrls()) .putList(URLS_SETTING, ldapUrls())

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.security.authc.ldap; package org.elasticsearch.xpack.security.authc.ldap;
import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.sdk.BindRequest;
import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck;
import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
@ -39,6 +38,8 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -87,8 +88,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, "", LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, "", LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", "") .put("user_search.base_dn", "")
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
} else { } else {
@ -105,11 +106,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
} }
}
public void testUserSearchSubTree() throws Exception { public void testUserSearchSubTree() throws Exception {
String groupSearchBase = "o=sevenSeas"; String groupSearchBase = "o=sevenSeas";
@ -120,8 +118,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
} else { } else {
@ -134,19 +132,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String user = "William Bush"; String user = "William Bush";
SecureString userPass = new SecureString("pass"); SecureString userPass = new SecureString("pass");
final SimpleBindRequest bindDNBindRequest = LdapUserSearchSessionFactory.bindRequest(config.settings());
try { try {
// auth // auth
try (LdapSession ldap = session(sessionFactory, user, userPass)) { try (LdapSession ldap = session(sessionFactory, user, userPass)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
//lookup //lookup
try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) { try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
@ -154,9 +151,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchBaseScopeFailsWithWrongBaseDN() throws Exception { public void testUserSearchBaseScopeFailsWithWrongBaseDN() throws Exception {
@ -168,9 +163,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.scope", LdapSearchScope.BASE) .put("user_search.scope", LdapSearchScope.BASE)
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
} else { } else {
@ -191,9 +186,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchBaseScopePassesWithCorrectBaseDN() throws Exception { public void testUserSearchBaseScopePassesWithCorrectBaseDN() throws Exception {
@ -204,9 +197,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.scope", LdapSearchScope.BASE) .put("user_search.scope", LdapSearchScope.BASE)
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
final boolean useAttribute = randomBoolean(); final boolean useAttribute = randomBoolean();
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
@ -220,19 +213,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String user = "William Bush"; String user = "William Bush";
SecureString userPass = new SecureString("pass"); SecureString userPass = new SecureString("pass");
final SimpleBindRequest bindDNBindRequest = LdapUserSearchSessionFactory.bindRequest(config.settings());
try { try {
// auth // auth
try (LdapSession ldap = session(sessionFactory, user, userPass)) { try (LdapSession ldap = session(sessionFactory, user, userPass)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
//lookup //lookup
try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) { try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
@ -240,9 +232,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchOneLevelScopeFailsWithWrongBaseDN() throws Exception { public void testUserSearchOneLevelScopeFailsWithWrongBaseDN() throws Exception {
@ -253,9 +243,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.scope", LdapSearchScope.ONE_LEVEL)
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
final boolean useAttribute = randomBoolean(); final boolean useAttribute = randomBoolean();
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
@ -277,9 +267,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchOneLevelScopePassesWithCorrectBaseDN() throws Exception { public void testUserSearchOneLevelScopePassesWithCorrectBaseDN() throws Exception {
@ -290,9 +278,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.scope", LdapSearchScope.ONE_LEVEL)
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
final boolean useAttribute = randomBoolean(); final boolean useAttribute = randomBoolean();
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "cn"); builder.put("user_search.attribute", "cn");
@ -306,19 +294,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String user = "William Bush"; String user = "William Bush";
SecureString userPass = new SecureString("pass"); SecureString userPass = new SecureString("pass");
final SimpleBindRequest bindDNBindRequest = LdapUserSearchSessionFactory.bindRequest(config.settings());
try { try {
//auth //auth
try (LdapSession ldap = session(sessionFactory, user, userPass)) { try (LdapSession ldap = session(sessionFactory, user, userPass)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
//lookup //lookup
try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) { try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString(user)); assertThat(dn, containsString(user));
} }
@ -326,9 +313,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchWithBadAttributeFails() throws Exception { public void testUserSearchWithBadAttributeFails() throws Exception {
@ -339,8 +324,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.pool.enabled", randomBoolean()); .put("user_search.pool.enabled", randomBoolean());
final boolean useLegacyBindPassword = configureBindPassword(builder);
final boolean useAttribute = randomBoolean(); final boolean useAttribute = randomBoolean();
if (useAttribute) { if (useAttribute) {
builder.put("user_search.attribute", "uid1"); builder.put("user_search.attribute", "uid1");
@ -362,61 +347,62 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
sessionFactory.close(); sessionFactory.close();
} }
if (useAttribute) { assertDeprecationWarnings(useAttribute, useLegacyBindPassword);
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE });
}
} }
public void testUserSearchWithoutAttributePasses() throws Exception { public void testUserSearchWithoutAttributePasses() throws Exception {
String groupSearchBase = "o=sevenSeas"; String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas"; String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", Settings.builder() final Settings.Builder realmSettings = Settings.builder()
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass") .put("user_search.pool.enabled", randomBoolean());
.put("user_search.pool.enabled", randomBoolean()) final boolean useLegacyBindPassword = configureBindPassword(realmSettings);
.build(), globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); RealmConfig config = new RealmConfig("ldap_realm", realmSettings.build(), globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
LdapUserSearchSessionFactory sessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool); LdapUserSearchSessionFactory sessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool);
String user = "wbush"; String user = "wbush";
SecureString userPass = new SecureString("pass"); SecureString userPass = new SecureString("pass");
final SimpleBindRequest bindDNBindRequest = LdapUserSearchSessionFactory.bindRequest(config.settings());
try { try {
//auth //auth
try (LdapSession ldap = session(sessionFactory, user, userPass)) { try (LdapSession ldap = session(sessionFactory, user, userPass)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString("William Bush")); assertThat(dn, containsString("William Bush"));
} }
//lookup //lookup
try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) { try (LdapSession ldap = unauthenticatedSession(sessionFactory, user)) {
assertConnectionValid(ldap.getConnection(), bindDNBindRequest); assertConnectionValid(ldap.getConnection(), sessionFactory.bindCredentials);
String dn = ldap.userDn(); String dn = ldap.userDn();
assertThat(dn, containsString("William Bush")); assertThat(dn, containsString("William Bush"));
} }
} finally { } finally {
sessionFactory.close(); sessionFactory.close();
} }
assertDeprecationWarnings(false, useLegacyBindPassword);
} }
public void testConnectionPoolDefaultSettings() throws Exception { public void testConnectionPoolDefaultSettings() throws Exception {
String groupSearchBase = "o=sevenSeas"; String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas"; String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", Settings.builder() final Settings.Builder realmSettings = Settings.builder()
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas");
.put("bind_password", "pass") configureBindPassword(realmSettings);
.build(), globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); RealmConfig config = new RealmConfig("ldap_realm", realmSettings.build(), globalSettings,
TestEnvironment.newEnvironment(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"), new SimpleBindRequest("cn=Horatio Hornblower,ou=people,o=sevenSeas", "pass"),
() -> "cn=Horatio Hornblower,ou=people,o=sevenSeas"); () -> "cn=Horatio Hornblower,ou=people,o=sevenSeas");
try { try {
assertThat(connectionPool.getCurrentAvailableConnections(), assertThat(connectionPool.getCurrentAvailableConnections(),
@ -435,19 +421,20 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
public void testConnectionPoolSettings() throws Exception { public void testConnectionPoolSettings() throws Exception {
String groupSearchBase = "o=sevenSeas"; String groupSearchBase = "o=sevenSeas";
String userSearchBase = "o=sevenSeas"; String userSearchBase = "o=sevenSeas";
RealmConfig config = new RealmConfig("ldap_realm", Settings.builder() final Settings.Builder realmSettings = Settings.builder()
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
.put("bind_password", "pass")
.put("user_search.pool.initial_size", 10) .put("user_search.pool.initial_size", 10)
.put("user_search.pool.size", 12) .put("user_search.pool.size", 12)
.put("user_search.pool.health_check.enabled", false) .put("user_search.pool.health_check.enabled", false);
.build(), globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); configureBindPassword(realmSettings);
RealmConfig config = new RealmConfig("ldap_realm", realmSettings.build(), globalSettings,
TestEnvironment.newEnvironment(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"), new SimpleBindRequest("cn=Horatio Hornblower,ou=people,o=sevenSeas", "pass"),
() -> "cn=Horatio Hornblower,ou=people,o=sevenSeas"); () -> "cn=Horatio Hornblower,ou=people,o=sevenSeas");
try { try {
assertThat(connectionPool.getCurrentAvailableConnections(), is(10)); assertThat(connectionPool.getCurrentAvailableConnections(), is(10));
@ -476,6 +463,8 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
searchSessionFactory.close(); searchSessionFactory.close();
} }
} }
assertDeprecationWarnings(false, true);
} }
public void testThatEmptyBindDNAndDisabledPoolingDoesNotThrow() throws Exception { public void testThatEmptyBindDNAndDisabledPoolingDoesNotThrow() throws Exception {
@ -499,22 +488,41 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
searchSessionFactory.close(); searchSessionFactory.close();
} }
} }
assertDeprecationWarnings(false, true);
} }
public void testEmptyBindDNReturnsAnonymousBindRequest() { public void testEmptyBindDNReturnsAnonymousBindRequest() throws LDAPException {
SimpleBindRequest request = LdapUserSearchSessionFactory.bindRequest(Settings.builder().put("bind_password", "password").build()); String groupSearchBase = "o=sevenSeas";
assertThat(request, is(notNullValue())); String userSearchBase = "o=sevenSeas";
assertThat(request.getBindDN(), isEmptyString()); final Settings.Builder realmSettings = Settings.builder()
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase);
final boolean useLegacyBindPassword = configureBindPassword(realmSettings);
RealmConfig config = new RealmConfig("ldap_realm", realmSettings.build(), globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
try (LdapUserSearchSessionFactory searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool)) {
assertThat(searchSessionFactory.bindCredentials, notNullValue());
assertThat(searchSessionFactory.bindCredentials.getBindDN(), isEmptyString());
}
assertDeprecationWarnings(false, useLegacyBindPassword);
} }
public void testThatBindRequestReturnsSimpleBindRequest() { public void testThatBindRequestReturnsSimpleBindRequest() throws LDAPException {
BindRequest request = LdapUserSearchSessionFactory.bindRequest(Settings.builder() String groupSearchBase = "o=sevenSeas";
.put("bind_password", "password") String userSearchBase = "o=sevenSeas";
final Settings.Builder realmSettings = Settings.builder()
.put(buildLdapSettings(ldapUrls(), Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE))
.put("bind_dn", "cn=ironman") .put("bind_dn", "cn=ironman")
.build()); .put("user_search.base_dn", userSearchBase);
assertEquals(request.getClass(), SimpleBindRequest.class); final boolean useLegacyBindPassword = configureBindPassword(realmSettings);
SimpleBindRequest simpleBindRequest = (SimpleBindRequest) request; RealmConfig config = new RealmConfig("ldap_realm", realmSettings.build(), globalSettings,
assertThat(simpleBindRequest.getBindDN(), is("cn=ironman")); TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
try (LdapUserSearchSessionFactory searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool)) {
assertThat(searchSessionFactory.bindCredentials, notNullValue());
assertThat(searchSessionFactory.bindCredentials.getBindDN(), is("cn=ironman"));
}
assertDeprecationWarnings(false, useLegacyBindPassword);
} }
public void testThatConnectErrorIsNotThrownOnConstruction() throws Exception { public void testThatConnectErrorIsNotThrownOnConstruction() throws Exception {
@ -526,20 +534,20 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
String ldapUrl = new LDAPURL("ldap", "localhost", inMemoryDirectoryServer.getListenPort(), null, null, null, null).toString(); String ldapUrl = new LDAPURL("ldap", "localhost", inMemoryDirectoryServer.getListenPort(), null, null, null, null).toString();
inMemoryDirectoryServer.shutDown(true); inMemoryDirectoryServer.shutDown(true);
Settings ldapSettings = Settings.builder() final Settings.Builder ldapSettingsBuilder = Settings.builder()
.put(LdapTestCase.buildLdapSettings(new String[] { ldapUrl }, Strings.EMPTY_ARRAY, .put(LdapTestCase.buildLdapSettings(new String[]{ldapUrl}, Strings.EMPTY_ARRAY,
groupSearchBase, LdapSearchScope.SUB_TREE)) groupSearchBase, LdapSearchScope.SUB_TREE))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("bind_dn", "ironman@ad.test.elasticsearch.com") .put("bind_dn", "ironman@ad.test.elasticsearch.com")
.put("bind_password", "password")
.put("user_search.attribute", "cn") .put("user_search.attribute", "cn")
.put("timeout.tcp_connect", "500ms") .put("timeout.tcp_connect", "500ms")
.put("type", "ldap") .put("type", "ldap")
.put("user_search.pool.health_check.enabled", false) .put("user_search.pool.health_check.enabled", false)
.put("user_search.pool.enabled", randomBoolean()) .put("user_search.pool.enabled", randomBoolean());
.build();
RealmConfig config = new RealmConfig("ldap_realm", ldapSettings, globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); final boolean useLegacyBindPassword = configureBindPassword(ldapSettingsBuilder);
RealmConfig config = new RealmConfig("ldap_realm", ldapSettingsBuilder.build(), globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
LdapUserSearchSessionFactory searchSessionFactory = null; LdapUserSearchSessionFactory searchSessionFactory = null;
try { try {
searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool); searchSessionFactory = getLdapUserSearchSessionFactory(config, sslService, threadPool);
@ -549,7 +557,30 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
} }
} }
assertSettingDeprecationsAndWarnings(new Setting[] { LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE }); assertDeprecationWarnings(true, useLegacyBindPassword);
}
private void assertDeprecationWarnings(boolean useAttribute, boolean legacyBindPassword) {
List<Setting<?>> deprecatedSettings = new ArrayList<>();
if (useAttribute) {
deprecatedSettings.add(LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE);
}
if (legacyBindPassword) {
deprecatedSettings.add(PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD);
}
if (deprecatedSettings.size() > 0) {
assertSettingDeprecationsAndWarnings(deprecatedSettings.toArray(new Setting<?>[deprecatedSettings.size()]));
}
}
private boolean configureBindPassword(Settings.Builder builder) {
final boolean useLegacyBindPassword = randomBoolean();
if (useLegacyBindPassword) {
builder.put("bind_password", "pass");
} else {
builder.setSecureSettings(newSecureSettings("secure_bind_password", "pass"));
}
return useLegacyBindPassword;
} }
static LdapUserSearchSessionFactory getLdapUserSearchSessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) static LdapUserSearchSessionFactory getLdapUserSearchSessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool)

View File

@ -9,6 +9,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
@ -17,6 +18,7 @@ import org.elasticsearch.test.OpenLdapTests;
import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
@ -41,6 +43,7 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
private Settings globalSettings; private Settings globalSettings;
private ThreadPool threadPool; private ThreadPool threadPool;
private MockSecureSettings globalSecureSettings;
@Before @Before
public void init() throws Exception { public void init() throws Exception {
@ -50,10 +53,11 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
* If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname * If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname
* verification tests since a re-established connection does not perform hostname verification. * verification tests since a re-established connection does not perform hostname verification.
*/ */
globalSecureSettings = newSecureSettings("xpack.ssl.truststore.secure_password", "changeit");
globalSettings = Settings.builder() globalSettings = Settings.builder()
.put("path.home", createTempDir()) .put("path.home", createTempDir())
.put("xpack.ssl.truststore.path", keystore) .put("xpack.ssl.truststore.path", keystore)
.setSecureSettings(newSecureSettings("xpack.ssl.truststore.secure_password", "changeit")) .setSecureSettings(globalSecureSettings)
.build(); .build();
threadPool = new TestThreadPool("LdapUserSearchSessionFactoryTests"); threadPool = new TestThreadPool("LdapUserSearchSessionFactoryTests");
} }
@ -63,26 +67,40 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
terminate(threadPool); terminate(threadPool);
} }
public void testUserSearchwithBindUserOpenLDAP() throws Exception { public void testUserSearchWithBindUserOpenLDAP() throws Exception {
final boolean useSecureBindPassword = randomBoolean();
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
RealmConfig config = new RealmConfig("oldap-test", Settings.builder() final Settings.Builder realmSettings = Settings.builder()
.put(LdapTestCase.buildLdapSettings(new String[] { OpenLdapTests.OPEN_LDAP_DNS_URL }, Strings.EMPTY_ARRAY, groupSearchBase, .put(LdapTestCase.buildLdapSettings(new String[]{OpenLdapTests.OPEN_LDAP_DNS_URL}, Strings.EMPTY_ARRAY, groupSearchBase,
LdapSearchScope.ONE_LEVEL)) LdapSearchScope.ONE_LEVEL))
.put("user_search.base_dn", userSearchBase) .put("user_search.base_dn", userSearchBase)
.put("group_search.user_attribute", "uid") .put("group_search.user_attribute", "uid")
.put("bind_dn", "uid=blackwidow,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") .put("bind_dn", "uid=blackwidow,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com")
.put("bind_password", OpenLdapTests.PASSWORD) .put("user_search.pool.enabled", randomBoolean());
.put("user_search.pool.enabled", randomBoolean()) if (useSecureBindPassword) {
.build(), globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString("secure_bind_password", OpenLdapTests.PASSWORD);
realmSettings.setSecureSettings(secureSettings);
} else {
realmSettings.put("bind_password", OpenLdapTests.PASSWORD);
}
RealmConfig config = new RealmConfig("oldap-test", realmSettings.build(), globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
Settings.Builder builder = Settings.builder() Settings.Builder builder = Settings.builder()
.put(globalSettings); .put(globalSettings, false);
builder.put(Settings.builder().put(config.settings()).normalizePrefix("xpack.security.authc.realms.ldap.").build()); builder.put(Settings.builder().put(config.settings(), false).normalizePrefix("xpack.security.authc.realms.ldap.").build());
final MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.merge(globalSecureSettings);
if (useSecureBindPassword) {
secureSettings.setString("xpack.security.authc.realms.ldap.secure_bind_password", OpenLdapTests.PASSWORD);
}
builder.setSecureSettings(secureSettings);
Settings settings = builder.build(); Settings settings = builder.build();
SSLService sslService = new SSLService(settings, TestEnvironment.newEnvironment(settings)); SSLService sslService = new SSLService(settings, TestEnvironment.newEnvironment(settings));
String[] users = new String[] { "cap", "hawkeye", "hulk", "ironman", "thor" }; String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor"};
try (LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, sslService, threadPool)) { try (LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, sslService, threadPool)) {
for (String user : users) { for (String user : users) {
//auth //auth
@ -100,6 +118,10 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
} }
} }
} }
if (useSecureBindPassword == false) {
assertSettingDeprecationsAndWarnings(new Setting<?>[]{PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD});
}
} }
private MockSecureSettings newSecureSettings(String key, String value) { private MockSecureSettings newSecureSettings(String key, String value) {

View File

@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction; import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest; import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
@ -28,19 +29,30 @@ import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswo
*/ */
public class ActiveDirectoryRunAsIT extends AbstractAdLdapRealmTestCase { public class ActiveDirectoryRunAsIT extends AbstractAdLdapRealmTestCase {
private static boolean useLegacyBindPassword;
@BeforeClass @BeforeClass
public static void selectRealmConfig() { public static void selectRealmConfig() {
realmConfig = RealmConfig.AD; realmConfig = RealmConfig.AD;
useLegacyBindPassword = randomBoolean();
} }
@Override @Override
protected Settings nodeSettings(int nodeOrdinal) { protected Settings nodeSettings(int nodeOrdinal) {
useLegacyBindPassword = randomBoolean();
final Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal)); final Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
switch (realmConfig) { switch (realmConfig) {
case AD: case AD:
builder.put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".bind_dn", "ironman@ad.test.elasticsearch.com") builder.put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".bind_dn", "ironman@ad.test.elasticsearch.com")
.put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".bind_password", ActiveDirectorySessionFactoryTests.PASSWORD)
.put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".user_search.pool.enabled", false); .put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".user_search.pool.enabled", false);
if (useLegacyBindPassword) {
builder.put(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".bind_password", ActiveDirectorySessionFactoryTests.PASSWORD);
} else {
SecuritySettingsSource.addSecureSettings(builder, secureSettings -> {
secureSettings.setString(XPACK_SECURITY_AUTHC_REALMS_EXTERNAL + ".secure_bind_password",
ActiveDirectorySessionFactoryTests.PASSWORD);
});
}
break; break;
default: default:
throw new IllegalStateException("Unknown realm config " + realmConfig); throw new IllegalStateException("Unknown realm config " + realmConfig);