org.elasticsearch.xpack.security.authc.ldap.LdapRealm Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of x-pack-security Show documentation
Show all versions of x-pack-security Show documentation
Elasticsearch Expanded Pack Plugin - Security
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authc.ldap;
import com.unboundid.ldap.sdk.LDAPException;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.core.security.authc.ldap.LdapSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapLoadBalancing;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession;
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport;
import org.elasticsearch.xpack.security.authc.support.DnRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper;
import org.elasticsearch.xpack.security.support.ReloadableSecurityComponent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.elasticsearch.core.Strings.format;
import static org.elasticsearch.xpack.security.authc.ldap.LdapUserSearchSessionFactory.configuredUserSearchSettings;
/**
* Authenticates username/password tokens against ldap, locates groups and maps them to roles.
*/
public final class LdapRealm extends CachingUsernamePasswordRealm implements ReloadableSecurityComponent {
private final SessionFactory sessionFactory;
private final UserRoleMapper roleMapper;
private final ThreadPool threadPool;
private final TimeValue executionTimeout;
private DelegatedAuthorizationSupport delegatedRealms;
public LdapRealm(
RealmConfig config,
SSLService sslService,
ResourceWatcherService watcherService,
UserRoleMapper userRoleMapper,
ThreadPool threadPool
) throws LDAPException {
this(
config,
sessionFactory(config, sslService, threadPool),
new CompositeRoleMapper(new DnRoleMapper(config, watcherService), userRoleMapper),
threadPool
);
}
// pkg private for testing
LdapRealm(RealmConfig config, SessionFactory sessionFactory, UserRoleMapper roleMapper, ThreadPool threadPool) {
super(config, threadPool);
this.sessionFactory = sessionFactory;
this.roleMapper = roleMapper;
this.threadPool = threadPool;
this.executionTimeout = config.getSetting(LdapRealmSettings.EXECUTION_TIMEOUT);
roleMapper.clearRealmCacheOnChange(this);
}
static SessionFactory sessionFactory(RealmConfig config, SSLService sslService, ThreadPool threadPool) throws LDAPException {
final SessionFactory sessionFactory;
if (LdapRealmSettings.AD_TYPE.equals(config.type())) {
sessionFactory = new ActiveDirectorySessionFactory(config, sslService, threadPool);
} else {
assert LdapRealmSettings.LDAP_TYPE.equals(config.type())
: "type ["
+ config.type()
+ "] is unknown. expected one of ["
+ LdapRealmSettings.AD_TYPE
+ ", "
+ LdapRealmSettings.LDAP_TYPE
+ "]";
final List extends Setting.AffixSetting>> configuredSearchSettings = configuredUserSearchSettings(config);
final boolean hasTemplates = config.hasSetting(LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING);
if (configuredSearchSettings.isEmpty()) {
if (hasTemplates == false) {
throw new IllegalArgumentException(
"settings were not found for either user search ["
+ RealmSettings.getFullSettingKey(config, LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN)
+ "] or user template ["
+ RealmSettings.getFullSettingKey(config, LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING)
+ "] modes of operation. "
+ "Please provide the settings for the mode you wish to use. For more details refer to the ldap "
+ "authentication section of the X-Pack guide."
);
}
sessionFactory = new LdapSessionFactory(config, sslService, threadPool);
} else if (hasTemplates) {
throw new IllegalArgumentException(
"settings were found for both user search ["
+ configuredSearchSettings.stream()
.map(s -> RealmSettings.getFullSettingKey(config, s))
.collect(Collectors.joining(","))
+ "] and user template ["
+ RealmSettings.getFullSettingKey(config, LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING)
+ "] modes of operation. "
+ "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap "
+ "authentication section of the X-Pack guide."
);
} else {
sessionFactory = new LdapUserSearchSessionFactory(config, sslService, threadPool);
}
}
return sessionFactory;
}
/**
* Given a username and password, open a connection to ldap, bind to authenticate, retrieve groups, map to roles and build the user.
* This user will then be passed to the listener
*/
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener> listener) {
assert delegatedRealms != null : "Realm has not been initialized correctly";
// we submit to the threadpool because authentication using LDAP will execute blocking I/O for a bind request and we don't want
// network threads stuck waiting for a socket to connect. After the bind, then all interaction with LDAP should be async
final CancellableLdapRunnable> cancellableLdapRunnable = new CancellableLdapRunnable<>(
listener,
ex -> AuthenticationResult.unsuccessful("Authentication against realm [" + this.toString() + "] failed", ex),
() -> sessionFactory.session(
token.principal(),
token.credentials(),
contextPreservingListener(new LdapSessionActionListener("authenticate", token.principal(), listener))
),
logger
);
threadPool.generic().execute(cancellableLdapRunnable);
threadPool.schedule(cancellableLdapRunnable::maybeTimeout, executionTimeout, EsExecutors.DIRECT_EXECUTOR_SERVICE);
}
@Override
protected void doLookupUser(String username, ActionListener userActionListener) {
if (sessionFactory.supportsUnauthenticatedSession()) {
// we submit to the threadpool because authentication using LDAP will execute blocking I/O for a bind request and we don't want
// network threads stuck waiting for a socket to connect. After the bind, then all interaction with LDAP should be async
final ActionListener> sessionListener = ActionListener.wrap(
result -> userActionListener.onResponse(result.getValue()),
userActionListener::onFailure
);
final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable<>(
userActionListener,
e -> null,
() -> sessionFactory.unauthenticatedSession(
username,
contextPreservingListener(new LdapSessionActionListener("lookup", username, sessionListener))
),
logger
);
threadPool.generic().execute(cancellableLdapRunnable);
threadPool.schedule(cancellableLdapRunnable::maybeTimeout, executionTimeout, EsExecutors.DIRECT_EXECUTOR_SERVICE);
} else {
userActionListener.onResponse(null);
}
}
/**
* Wraps the provided sessionListener
to preserve the {@link ThreadContext} associated with the
* current thread.
* Responses headers are not preserved, as they are not needed. Response output should not yet exist, nor should
* any be produced within the realm/ldap-session.
*/
private ContextPreservingActionListener contextPreservingListener(LdapSessionActionListener sessionListener) {
final Supplier toRestore = config.threadContext().newRestorableContext(false);
return new ContextPreservingActionListener<>(toRestore, sessionListener);
}
@Override
public void initialize(Iterable realms, XPackLicenseState licenseState) {
if (delegatedRealms != null) {
throw new IllegalStateException("Realm has already been initialized");
}
delegatedRealms = new DelegatedAuthorizationSupport(realms, config, licenseState);
}
@Override
public void usageStats(ActionListener