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.
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]
------------------------------------------------------------
@ -107,12 +107,20 @@ xpack:
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.
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.
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,
`bind_password` is not exposed via the
{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.
For supported values see <<ad-load-balancing>>.
| `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
url: "ldaps://ldap.example.com:636"
bind_dn: "cn=ldapuser, ou=users, o=services, dc=example, dc=com"
bind_password: x-pack-test-password
user_search:
base_dn: "dc=example,dc=com"
attribute: cn
@ -72,6 +71,15 @@ xpack:
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
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.
@ -281,9 +289,12 @@ failover and load balancing modes of operation.
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 the
LDAP. Due to its potential security impact,
LDAP 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].
*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.scope` | no | The scope of the user search. Valid values are `sub_tree`,
`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
support this out of the box. The LDAP realm must be configured to run in
<<ldap-user-search, _user search_ mode>>. The Active Directory realm must be
<<ad-settings,configured with a `bind_dn` and `bind_password`>> to support _run as_.
The PKI realm does not support _run as_.
<<ad-settings,configured with a `bind_dn` and `secure_bind_password`>> to support
_run as_. The PKI realm does not support _run as_.
To submit requests on behalf of other users, you need to have the `run_as`
permission. For example, the following role grants permission to submit request

View File

@ -182,7 +182,12 @@ If this is not specified, an anonymous bind will be attempted.
Defaults to Empty.
`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.
`user_dn_templates`::
@ -410,6 +415,11 @@ Defaults to Empty.
`bind_password`::
The password for the user that will be used to bind to Active 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 Active Directory.
Defaults to Empty.
`unmapped_groups_as_roles`::
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;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.TimeValue;
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.Set;
import static org.elasticsearch.common.settings.SecureSetting.secureString;
public final class PoolingSessionFactorySettings {
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_PASSWORD = Setting.simpleString("bind_password", Setting.Property.NodeScope,
Setting.Property.Filtered);
public static final Setting<SecureString> LEGACY_BIND_PASSWORD = new Setting<>("bind_password", "", SecureString::new,
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 Setting<Integer> POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size",
DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope);
@ -34,6 +39,6 @@ public final class PoolingSessionFactorySettings {
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);
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.controls.AuthorizationIdentityRequestControl;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener;
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.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
@ -66,23 +66,18 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
ActiveDirectorySessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException {
super(config, sslService, new ActiveDirectoryGroupsResolver(config.settings()),
ActiveDirectorySessionFactorySettings.POOL_ENABLED, () -> {
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) {
return new SimpleBindRequest(getBindDN(config.settings()),
PoolingSessionFactorySettings.BIND_PASSWORD.get(config.settings()));
} else {
return new SimpleBindRequest();
}
}, () -> {
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) {
final String healthCheckDn = PoolingSessionFactorySettings.BIND_DN.get(config.settings());
if (healthCheckDn.isEmpty() && healthCheckDn.indexOf('=') > 0) {
return healthCheckDn;
}
}
return config.settings().get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_BASEDN_SETTING,
config.settings().get(ActiveDirectorySessionFactorySettings.AD_DOMAIN_NAME_SETTING));
}, threadPool);
ActiveDirectorySessionFactorySettings.POOL_ENABLED,
PoolingSessionFactorySettings.BIND_DN.exists(config.settings())? getBindDN(config.settings()) : null,
() -> {
if (PoolingSessionFactorySettings.BIND_DN.exists(config.settings())) {
final String healthCheckDn = PoolingSessionFactorySettings.BIND_DN.get(config.settings());
if (healthCheckDn.isEmpty() && healthCheckDn.indexOf('=') > 0) {
return healthCheckDn;
}
}
return config.settings().get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_BASEDN_SETTING,
config.settings().get(ActiveDirectorySessionFactorySettings.AD_DOMAIN_NAME_SETTING));
}, threadPool);
Settings settings = config.settings();
String domainName = settings.get(ActiveDirectorySessionFactorySettings.AD_DOMAIN_NAME_SETTING);
if (domainName == null) {
@ -155,9 +150,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
}
try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = new SimpleBindRequest(getBindDN(config.settings()),
PoolingSessionFactorySettings.BIND_PASSWORD.get(config.settings()));
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
@Override
public void onFailure(Exception e) {
@ -225,7 +218,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
final LdapSearchScope userSearchScope;
final String userSearchFilter;
final String bindDN;
final String bindPassword; // TODO this needs to be a setting in the secure settings store!
final SecureString bindPassword;
final ThreadPool threadPool;
ADAuthenticator(RealmConfig realm, TimeValue timeout, boolean ignoreReferralErrors, Logger logger, GroupsResolver groupsResolver,
@ -239,7 +232,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
this.metaDataResolver = metaDataResolver;
final Settings settings = realm.settings();
this.bindDN = getBindDN(settings);
this.bindPassword = PoolingSessionFactorySettings.BIND_PASSWORD.get(settings);
this.bindPassword = PoolingSessionFactorySettings.SECURE_BIND_PASSWORD.get(settings);
this.threadPool = threadPool;
userSearchDN = settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_BASEDN_SETTING, domainDN);
userSearchScope = LdapSearchScope.resolve(settings.get(ActiveDirectorySessionFactorySettings.AD_USER_SEARCH_SCOPE_SETTING),
@ -274,7 +267,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
if (bindDN.isEmpty()) {
searchRunnable.run();
} else {
final SimpleBindRequest bind = new SimpleBindRequest(bindDN, bindPassword);
final SimpleBindRequest bind = new SimpleBindRequest(bindDN, CharArrays.toUtf8Bytes(bindPassword.getChars()));
LdapUtils.maybeForkThenBind(connection, bind, threadPool, searchRunnable);
}
}
@ -439,7 +432,7 @@ class ActiveDirectorySessionFactory extends PoolingSessionFactory {
final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars());
final SimpleBindRequest bind = bindDN.isEmpty()
? new SimpleBindRequest(username, passwordBytes)
: new SimpleBindRequest(bindDN, bindPassword);
: new SimpleBindRequest(bindDN, CharArrays.toUtf8Bytes(bindPassword.getChars()));
LdapUtils.maybeForkThenBind(searchConnection, bind, threadPool, new ActionRunnable<String>(listener) {
@Override
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.SearchResultEntry;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRunnable;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
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.PoolingSessionFactorySettings;
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.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.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.createFilter;
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 {
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())) {
return PoolingSessionFactorySettings.BIND_DN.get(config.settings());
if (BIND_DN.exists(config.settings())) {
return BIND_DN.get(config.settings());
} else {
return LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN.get(config.settings());
}
@ -66,15 +66,6 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
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) {
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) {
try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = bindRequest(config.settings());
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
findUser(user, connection, ActionListener.wrap((entry) -> {
@ -133,7 +123,7 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
LdapUtils.maybeForkThenBind(connection, userBind, threadPool, new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
@ -195,8 +185,7 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory {
void getUnauthenticatedSessionWithoutPool(String user, ActionListener<LdapSession> listener) {
try {
final LDAPConnection connection = LdapUtils.privilegedConnect(serverSet::getConnection);
final SimpleBindRequest bind = bindRequest(config.settings());
LdapUtils.maybeForkThenBind(connection, bind, threadPool, new AbstractRunnable() {
LdapUtils.maybeForkThenBind(connection, bindCredentials, threadPool, new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
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.LDAPException;
import com.unboundid.ldap.sdk.ServerSet;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.lease.Releasable;
import org.elasticsearch.common.settings.SecureString;
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.RealmSettings;
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.security.authc.ldap.support.LdapMetaDataResolver;
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 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
*/
@ -39,6 +45,7 @@ abstract class PoolingSessionFactory extends SessionFactory implements Releasabl
private final boolean useConnectionPool;
private final LDAPConnectionPool connectionPool;
final SimpleBindRequest bindCredentials;
final LdapMetaDataResolver metaDataResolver;
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 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 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 threadPool a thread pool used for async queries execution
*/
PoolingSessionFactory(RealmConfig config, SSLService sslService, LdapSession.GroupsResolver groupResolver,
Setting<Boolean> poolingEnabled, Supplier<BindRequest> bindRequestSupplier, Supplier<String> healthCheckDNSupplier,
ThreadPool threadPool) throws LDAPException {
Setting<Boolean> poolingEnabled, @Nullable String bindDn, Supplier<String> healthCheckDNSupplier,
ThreadPool threadPool) throws LDAPException {
super(config, sslService, threadPool);
this.groupResolver = groupResolver;
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());
if (useConnectionPool) {
this.connectionPool = createConnectionPool(config, serverSet, timeout, logger, bindRequestSupplier, healthCheckDNSupplier);
this.connectionPool = createConnectionPool(config, serverSet, timeout, logger, bindCredentials, healthCheckDNSupplier);
} else {
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
*/
static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, Logger logger,
Supplier<BindRequest> bindRequestSupplier,
BindRequest bindRequest,
Supplier<String> healthCheckDnSupplier) throws LDAPException {
Settings settings = config.settings();
BindRequest bindRequest = bindRequestSupplier.get();
final int initialSize = PoolingSessionFactorySettings.POOL_INITIAL_SIZE.get(settings);
final int size = PoolingSessionFactorySettings.POOL_SIZE.get(settings);
LDAPConnectionPool pool = null;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.test;
import org.bouncycastle.operator.OperatorCreationException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Guice;
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.SettingsFilter;
import org.elasticsearch.common.settings.SettingsModule;
import org.elasticsearch.xpack.core.XPackPlugin;
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.Security;
import org.hamcrest.Matcher;
import javax.net.ssl.KeyManagerFactory;
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.HashMap;
import java.util.List;
@ -40,6 +35,8 @@ public class SettingsFilterTests extends ESTestCase {
private MockSecureSettings mockSecureSettings = new MockSecureSettings();
public void testFiltering() throws Exception {
final boolean useLegacyLdapBindPassword = randomBoolean();
configureUnfilteredSetting("xpack.security.authc.realms.file.type", "file");
// ldap realm filtering
@ -48,7 +45,11 @@ public class SettingsFilterTests extends ESTestCase {
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.bind_dn", randomAlphaOfLength(5));
configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_password", randomAlphaOfLength(5));
if (useLegacyLdapBindPassword) {
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
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
configureFilteredSetting("foo.bar", "_secret");
configureFilteredSetting("foo.baz", "_secret");;
configureFilteredSetting("foo.baz", "_secret");
configureFilteredSetting("bar.baz", "_secret");
configureUnfilteredSetting("baz.foo", "_not_a_secret");
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()) {
assertThat(filteredSettings.get(entry.getKey()), entry.getValue());
}
if (useLegacyLdapBindPassword) {
assertSettingDeprecationsAndWarnings(new Setting<?>[]{PoolingSessionFactorySettings.LEGACY_BIND_PASSWORD});
}
}
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.SecuritySettingsSource;
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.support.Hasher;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import java.util.ArrayList;
import java.util.Arrays;
@ -138,9 +138,12 @@ public class RealmSettingsTests extends ESTestCase {
private Settings.Builder ldapSettings(boolean userSearch, boolean groupSearch) {
final Settings.Builder builder = commonLdapSettings("ldap", true)
.put("bind_dn", "elasticsearch")
.put("bind_password", "t0p_s3cr3t")
.put("follow_referrals", randomBoolean());
SecuritySettingsSource.addSecureSettings(builder, secureSettings -> {
secureSettings.setString("bind_password", "t0p_s3cr3t");
});
if (userSearch) {
builder.put("user_search.base_dn", "o=people, dc=example, dc=com");
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.support.PlainActionFuture;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
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
*
* <p>
* 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
* 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.
*
* <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
* 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 {
Settings settings = settings(Settings.builder()
final Settings.Builder builder = Settings.builder()
.put(ActiveDirectorySessionFactorySettings.POOL_ENABLED.getKey(), pooled)
.put(PoolingSessionFactorySettings.BIND_DN.getKey(), "CN=ironman@ad.test.elasticsearch.com")
.put(PoolingSessionFactorySettings.BIND_PASSWORD.getKey(), PASSWORD)
.build());
.put(PoolingSessionFactorySettings.BIND_DN.getKey(), "CN=ironman@ad.test.elasticsearch.com");
final boolean useLegacyBindPassword = randomBoolean();
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,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
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 org.elasticsearch.action.ActionListener;
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.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -229,7 +231,7 @@ public class LdapRealmTests extends LdapTestCase {
.putList(URLS_SETTING, ldapUrls())
.put("user_search.base_dn", "")
.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.scope", LdapSearchScope.SUB_TREE)
.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 {
Settings settings = Settings.builder()
.putList(URLS_SETTING, ldapUrls())

View File

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

View File

@ -9,6 +9,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.TestEnvironment;
@ -17,6 +18,7 @@ import org.elasticsearch.test.OpenLdapTests;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
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.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
@ -41,6 +43,7 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
private Settings globalSettings;
private ThreadPool threadPool;
private MockSecureSettings globalSecureSettings;
@Before
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
* verification tests since a re-established connection does not perform hostname verification.
*/
globalSecureSettings = newSecureSettings("xpack.ssl.truststore.secure_password", "changeit");
globalSettings = Settings.builder()
.put("path.home", createTempDir())
.put("xpack.ssl.truststore.path", keystore)
.setSecureSettings(newSecureSettings("xpack.ssl.truststore.secure_password", "changeit"))
.setSecureSettings(globalSecureSettings)
.build();
threadPool = new TestThreadPool("LdapUserSearchSessionFactoryTests");
}
@ -63,26 +67,40 @@ public class OpenLdapUserSearchSessionFactoryTests extends ESTestCase {
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 userSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
RealmConfig config = new RealmConfig("oldap-test", Settings.builder()
.put(LdapTestCase.buildLdapSettings(new String[] { OpenLdapTests.OPEN_LDAP_DNS_URL }, Strings.EMPTY_ARRAY, groupSearchBase,
final Settings.Builder realmSettings = Settings.builder()
.put(LdapTestCase.buildLdapSettings(new String[]{OpenLdapTests.OPEN_LDAP_DNS_URL}, Strings.EMPTY_ARRAY, groupSearchBase,
LdapSearchScope.ONE_LEVEL))
.put("user_search.base_dn", userSearchBase)
.put("group_search.user_attribute", "uid")
.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())
.build(), globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
.put("user_search.pool.enabled", randomBoolean());
if (useSecureBindPassword) {
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()
.put(globalSettings);
builder.put(Settings.builder().put(config.settings()).normalizePrefix("xpack.security.authc.realms.ldap.").build());
.put(globalSettings, false);
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();
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)) {
for (String user : users) {
//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) {

View File

@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.collect.MapBuilder;
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.AuthenticateRequest;
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 {
private static boolean useLegacyBindPassword;
@BeforeClass
public static void selectRealmConfig() {
realmConfig = RealmConfig.AD;
useLegacyBindPassword = randomBoolean();
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
useLegacyBindPassword = randomBoolean();
final Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
switch (realmConfig) {
case AD:
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);
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;
default:
throw new IllegalStateException("Unknown realm config " + realmConfig);