diff --git a/docs/changelog/89492.yaml b/docs/changelog/89492.yaml new file mode 100644 index 000000000000..b5016f44fd18 --- /dev/null +++ b/docs/changelog/89492.yaml @@ -0,0 +1,5 @@ +pr: 89492 +summary: More accurate error message for LDAP user modes +area: Authentication +type: enhancement +issues: [] diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index 42d860edbc05..0bf7146600ca 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -13,6 +13,7 @@ import org.elasticsearch.ElasticsearchTimeoutException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.IOUtils; @@ -41,14 +42,17 @@ import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupp import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; +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. @@ -101,9 +105,9 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { + ", " + LdapRealmSettings.LDAP_TYPE + "]"; - final boolean hasSearchSettings = LdapUserSearchSessionFactory.hasUserSearchSettings(config); + final List> configuredSearchSettings = configuredUserSearchSettings(config); final boolean hasTemplates = config.hasSetting(LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING); - if (hasSearchSettings == false) { + if (configuredSearchSettings.isEmpty()) { if (hasTemplates == false) { throw new IllegalArgumentException( "settings were not found for either user search [" @@ -119,7 +123,9 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { } else if (hasTemplates) { throw new IllegalArgumentException( "settings were found for both user search [" - + RealmSettings.getFullSettingKey(config, LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN) + + 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. " @@ -206,7 +212,7 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { usage.put("size", getCacheSize()); usage.put("load_balance_type", LdapLoadBalancing.resolve(config).toString()); usage.put("ssl", sessionFactory.isSslUsed()); - usage.put("user_search", LdapUserSearchSessionFactory.hasUserSearchSettings(config)); + usage.put("user_search", false == configuredUserSearchSettings(config).isEmpty()); listener.onResponse(usage); }, listener::onFailure)); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java index 99d4321e4c29..d177ffbefebf 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java @@ -17,6 +17,7 @@ import com.unboundid.ldap.sdk.SimpleBindRequest; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.core.CharArrays; import org.elasticsearch.core.IOUtils; @@ -31,6 +32,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 java.util.List; import java.util.stream.Stream; import static org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings.BIND_DN; @@ -41,7 +43,6 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear class LdapUserSearchSessionFactory extends PoolingSessionFactory { static final String SEARCH_PREFIX = "user_search."; - private final String userSearchBaseDn; private final LdapSearchScope scope; private final String searchFilter; @@ -66,14 +67,14 @@ class LdapUserSearchSessionFactory extends PoolingSessionFactory { logger.info("Realm [{}] is in user-search mode - base_dn=[{}], search filter=[{}]", config.name(), userSearchBaseDn, searchFilter); } - static boolean hasUserSearchSettings(RealmConfig config) { + static List> configuredUserSearchSettings(RealmConfig config) { return Stream.of( LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN, LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE, LdapUserSearchSessionFactorySettings.SEARCH_SCOPE, LdapUserSearchSessionFactorySettings.SEARCH_FILTER, LdapUserSearchSessionFactorySettings.POOL_ENABLED - ).anyMatch(config::hasSetting); + ).filter(config::hasSetting).toList(); } /** diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index c39e2be2841a..34c9a7e3b0b0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -59,9 +59,11 @@ import org.junit.Before; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import static org.elasticsearch.test.ActionListenerUtils.anyActionListener; import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; @@ -368,23 +370,47 @@ public class LdapRealmTests extends LdapTestCase { LdapRealmSettings.LDAP_TYPE, "test-ldap-realm-user-search" ); - Settings settings = Settings.builder() + + final List> userSearchSettings = List.of( + LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN, + LdapUserSearchSessionFactorySettings.SEARCH_ATTRIBUTE, + LdapUserSearchSessionFactorySettings.SEARCH_SCOPE, + LdapUserSearchSessionFactorySettings.SEARCH_FILTER, + LdapUserSearchSessionFactorySettings.POOL_ENABLED + ); + final List> configuredUserSearchSettings = randomNonEmptySubsetOf(userSearchSettings).stream() + .sorted(Comparator.comparing(userSearchSettings::indexOf)) + .toList(); + + final Settings.Builder settingsBuilder = Settings.builder() .put(defaultGlobalSettings) .putList(getFullSettingKey(identifier, URLS_SETTING), ldapUrls()) .putList(getFullSettingKey(identifier.getName(), LdapSessionFactorySettings.USER_DN_TEMPLATES_SETTING), "cn=foo") - .put(getFullSettingKey(identifier.getName(), LdapUserSearchSessionFactorySettings.SEARCH_BASE_DN), "cn=bar") .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.BASE_DN), "") .put(getFullSettingKey(identifier, SearchGroupsResolverSettings.SCOPE), LdapSearchScope.SUB_TREE) .put(getFullSettingKey(identifier, VERIFICATION_MODE_SETTING_REALM), SslVerificationMode.CERTIFICATE) - .put(getFullSettingKey(identifier, RealmSettings.ORDER_SETTING), 0) - .build(); - RealmConfig config = getRealmConfig(identifier, settings); + .put(getFullSettingKey(identifier, RealmSettings.ORDER_SETTING), 0); + + configuredUserSearchSettings.forEach(s -> { + final String key = getFullSettingKey(identifier.getName(), s); + settingsBuilder.put(key, key.endsWith(".enabled") ? String.valueOf(randomBoolean()) : randomAlphaOfLengthBetween(8, 18)); + }); + RealmConfig config = getRealmConfig(identifier, settingsBuilder.build()); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> LdapRealm.sessionFactory(config, null, threadPool)); + assertThat( e.getMessage(), containsString( "settings were found for both" - + " user search [xpack.security.authc.realms.ldap.test-ldap-realm-user-search.user_search.base_dn] and" + + " user search [" + + configuredUserSearchSettings.stream() + .map(Setting::getKey) + .map( + key -> "xpack.security.authc.realms.ldap.test-ldap-realm-user-search" + + key.substring(key.lastIndexOf(".user_search.")) + ) + .collect(Collectors.joining(",")) + + "] and" + " user template [xpack.security.authc.realms.ldap.test-ldap-realm-user-search.user_dn_templates]" ) );