From 7192c4630772fc205ca005648adf9667dbd48d17 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 13 Dec 2016 16:14:02 +1100 Subject: [PATCH] Define explicit settings for security realms/ssl (elastic/elasticsearch#4311) Modified the definition and loading of settings in Security to provide early detection and failure of invalid (unrecognised or syntactically invalid) settings. Also consolidates the number of places where settings were defined. Each realm now defines its supported settings. This is facilitated for custom realms via a new "getRealmSettings" method on XPackExtension. The realm group setting performs validation of the child settings with reference to the "type". For backwards compatibility reasons, realm types that have no configuration defined, will be accepted during validation, but may fail at realm creation time. All SSL settings have been centralised into SSLConfigurationSettings, which supports a variable "prefix" to accommodate the multiple places we define SSL config. HTTP Proxy settings are explicitly defined rather than being a generic group. Where possible all security settings now reference a Setting object, and there are less magic strings scattered in the code. Closes: elastic/elasticsearch#3965 Original commit: elastic/x-pack-elasticsearch@2c76a137a9688851d93fabf11bd1ace7ccc1b588 --- .../org/elasticsearch/xpack/XPackPlugin.java | 8 +- .../elasticsearch/xpack/XPackSettings.java | 186 ++--------- .../xpack/common/http/HttpClient.java | 28 +- .../xpack/common/http/HttpSettings.java | 49 +++ .../xpack/extensions/XPackExtension.java | 13 + .../xpack/security/Security.java | 22 +- .../xpack/security/authc/InternalRealms.java | 91 ++++++ .../xpack/security/authc/RealmConfig.java | 4 +- .../xpack/security/authc/RealmSettings.java | 154 ++++++++++ .../xpack/security/authc/Realms.java | 21 +- .../security/authc/esnative/NativeRealm.java | 11 + .../xpack/security/authc/file/FileRealm.java | 10 + .../ldap/ActiveDirectorySessionFactory.java | 15 + .../xpack/security/authc/ldap/LdapRealm.java | 50 ++- .../authc/ldap/LdapSessionFactory.java | 30 +- .../ldap/LdapUserSearchSessionFactory.java | 95 ++++-- .../authc/ldap/SearchGroupsResolver.java | 38 ++- .../ldap/UserAttributeGroupsResolver.java | 11 +- .../authc/ldap/support/LdapLoadBalancing.java | 16 +- .../authc/ldap/support/SessionFactory.java | 19 ++ .../xpack/security/authc/pki/PkiRealm.java | 63 ++-- .../support/CachingUsernamePasswordRealm.java | 28 +- .../security/authc/support/DnRoleMapper.java | 20 +- .../xpack/ssl/SSLConfiguration.java | 103 ++----- .../xpack/ssl/SSLConfigurationSettings.java | 135 ++++++++ .../elasticsearch/xpack/ssl/SSLService.java | 9 +- .../test/SettingsFilterTests.java | 8 +- .../xpack/common/http/HttpClientTests.java | 8 +- .../xpack/security/SecurityTests.java | 2 +- .../security/authc/RealmSettingsTests.java | 288 ++++++++++++++++++ .../authc/ldap/ActiveDirectoryRealmTests.java | 11 +- .../security/authc/ldap/LdapRealmTests.java | 37 ++- .../authc/ldap/SearchGroupsResolverTests.java | 38 +-- .../authc/ldap/support/LdapTestCase.java | 10 +- .../CachingUsernamePasswordRealmTests.java | 6 +- .../authc/support/DnRoleMapperTests.java | 11 +- .../ssl/SSLConfigurationSettingsTests.java | 103 +++++++ .../xpack/ssl/SSLServiceTests.java | 16 + 38 files changed, 1340 insertions(+), 427 deletions(-) create mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpSettings.java create mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java create mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmSettings.java create mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettings.java create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettingsTests.java diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java index 0ba72937d1b..51dae728b70 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackPlugin.java @@ -52,6 +52,7 @@ import org.elasticsearch.xpack.action.XPackInfoAction; import org.elasticsearch.xpack.action.XPackUsageAction; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; +import org.elasticsearch.xpack.common.http.HttpSettings; import org.elasticsearch.xpack.common.http.auth.HttpAuthFactory; import org.elasticsearch.xpack.common.http.auth.HttpAuthRegistry; import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth; @@ -316,7 +317,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I @Override public List> getSettings() { ArrayList> settings = new ArrayList<>(); - settings.addAll(Security.getSettings(transportClientMode)); + settings.addAll(Security.getSettings(transportClientMode, extensionsService)); settings.addAll(MonitoringSettings.getSettings()); settings.addAll(watcher.getSettings()); settings.addAll(licensing.getSettings()); @@ -335,10 +336,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I settings.add(ReportingAttachmentParser.INTERVAL_SETTING); // http settings - settings.add(Setting.simpleString("xpack.http.default_read_timeout", Setting.Property.NodeScope)); - settings.add(Setting.simpleString("xpack.http.default_connection_timeout", Setting.Property.NodeScope)); - settings.add(Setting.groupSetting("xpack.http.ssl.", Setting.Property.NodeScope)); - settings.add(Setting.groupSetting("xpack.http.proxy.", Setting.Property.NodeScope)); + settings.addAll(HttpSettings.getSettings()); return settings; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java index 382720e8255..05fa47e12b5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/XPackSettings.java @@ -5,24 +5,19 @@ */ package org.elasticsearch.xpack; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.ssl.SSLClientAuth; +import org.elasticsearch.xpack.ssl.SSLConfigurationSettings; +import org.elasticsearch.xpack.ssl.VerificationMode; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.function.Function; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.ssl.SSLClientAuth; -import org.elasticsearch.xpack.ssl.VerificationMode; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.TrustManagerFactory; - -import static java.util.Collections.emptyList; - /** * A container for xpack setting constants. */ @@ -74,170 +69,23 @@ public class XPackSettings { public static final VerificationMode VERIFICATION_MODE_DEFAULT = VerificationMode.FULL; // global settings that apply to everything! - private static final Setting> CIPHERS_SETTING = Setting.listSetting("xpack.ssl.cipher_suites", DEFAULT_CIPHERS, - Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> SUPPORTED_PROTOCOLS_SETTING = Setting.listSetting("xpack.ssl.supported_protocols", - DEFAULT_SUPPORTED_PROTOCOLS, Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting CLIENT_AUTH_SETTING = new Setting<>("xpack.ssl.client_authentication", - CLIENT_AUTH_DEFAULT.name(), SSLClientAuth::parse, Property.NodeScope, Property.Filtered); - private static final Setting VERIFICATION_MODE_SETTING = new Setting<>("xpack.ssl.verification_mode", - VERIFICATION_MODE_DEFAULT.name(), VerificationMode::parse, Property.NodeScope, Property.Filtered); - private static final Setting> KEYSTORE_PATH_SETTING = new Setting<>("xpack.ssl.keystore.path", - s -> System.getProperty("javax.net.ssl.keyStore"), Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> KEYSTORE_PASSWORD_SETTING = new Setting<>("xpack.ssl.keystore.password", - s -> System.getProperty("javax.net.ssl.keyStorePassword"), Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting KEYSTORE_ALGORITHM_SETTING = new Setting<>("xpack.ssl.keystore.algorithm", - s -> System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()), - Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> KEYSTORE_KEY_PASSWORD_SETTING = - new Setting<>("xpack.ssl.keystore.key_password", KEYSTORE_PASSWORD_SETTING, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting> TRUSTSTORE_PATH_SETTING = new Setting<>("xpack.ssl.truststore.path", - s -> System.getProperty("javax.net.ssl.trustStore"), Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> TRUSTSTORE_PASSWORD_SETTING = new Setting<>("xpack.ssl.truststore.password", - s -> System.getProperty("javax.net.ssl.trustStorePassword"), Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting TRUSTSTORE_ALGORITHM_SETTING = new Setting<>("xpack.ssl.truststore.algorithm", - s -> System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()), - Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> KEY_PATH_SETTING = - new Setting<>("xpack.ssl.key", (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> KEY_PASSWORD_SETTING = - new Setting<>("xpack.ssl.key_passphrase", (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> CERT_SETTING = - new Setting<>("xpack.ssl.certificate", (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> CA_PATHS_SETTING = Setting.listSetting("xpack.ssl.certificate_authorities", - Collections.emptyList(), s -> s, Property.NodeScope, Property.Filtered); + public static final String GLOBAL_SSL_PREFIX = "xpack.ssl."; + private static final SSLConfigurationSettings GLOBAL_SSL = SSLConfigurationSettings.withPrefix(GLOBAL_SSL_PREFIX); // http specific settings - private static final Setting> HTTP_CIPHERS_SETTING = Setting.listSetting("xpack.security.http.ssl.cipher_suites", - DEFAULT_CIPHERS, Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_SUPPORTED_PROTOCOLS_SETTING = - Setting.listSetting("xpack.security.http.ssl.supported_protocols", emptyList(), Function.identity(), - Property.NodeScope, Property.Filtered); - private static final Setting HTTP_CLIENT_AUTH_SETTING = new Setting<>("xpack.security.http.ssl.client_authentication", - CLIENT_AUTH_DEFAULT.name(), SSLClientAuth::parse, Property.NodeScope, Property.Filtered); - private static final Setting HTTP_VERIFICATION_MODE_SETTING = - new Setting<>("xpack.security.http.ssl.verification_mode", VERIFICATION_MODE_DEFAULT.name(), VerificationMode::parse, - Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_KEYSTORE_PATH_SETTING = new Setting<>("xpack.security.http.ssl.keystore.path", - (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_KEYSTORE_PASSWORD_SETTING = - new Setting<>("xpack.security.http.ssl.keystore.password", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting HTTP_KEYSTORE_ALGORITHM_SETTING = new Setting<>("xpack.security.http.ssl.keystore.algorithm", - "", Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_KEYSTORE_KEY_PASSWORD_SETTING = - new Setting<>("xpack.security.http.ssl.keystore.key_password", HTTP_KEYSTORE_PASSWORD_SETTING, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_TRUSTSTORE_PATH_SETTING = new Setting<>("xpack.security.http.ssl.truststore.path", - (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_TRUSTSTORE_PASSWORD_SETTING = - new Setting<>("xpack.security.http.ssl.truststore.password", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting HTTP_TRUSTSTORE_ALGORITHM_SETTING = new Setting<>("xpack.security.http.ssl.truststore.algorithm", - "", Function.identity(), Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_KEY_PATH_SETTING = - new Setting<>("xpack.security.http.ssl.key", (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_KEY_PASSWORD_SETTING = new Setting<>("xpack.security.http.ssl.key_passphrase", - (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_CERT_SETTING = new Setting<>("xpack.security.http.ssl.certificate", - (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> HTTP_CA_PATHS_SETTING = - Setting.listSetting("xpack.security.http.ssl.certificate_authorities", emptyList(), s -> s, - Property.NodeScope, Property.Filtered); + public static final String HTTP_SSL_PREFIX = Security.setting("http.ssl."); + private static final SSLConfigurationSettings HTTP_SSL = SSLConfigurationSettings.withPrefix(HTTP_SSL_PREFIX); // transport specific settings - private static final Setting> TRANSPORT_CIPHERS_SETTING = - Setting.listSetting("xpack.security.transport.ssl.cipher_suites", DEFAULT_CIPHERS, Function.identity(), - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_SUPPORTED_PROTOCOLS_SETTING = - Setting.listSetting("xpack.security.transport.ssl.supported_protocols", emptyList(), Function.identity(), - Property.NodeScope, Property.Filtered); - private static final Setting TRANSPORT_CLIENT_AUTH_SETTING = - new Setting<>("xpack.security.transport.ssl.client_authentication", CLIENT_AUTH_DEFAULT.name(), SSLClientAuth::parse, - Property.NodeScope, Property.Filtered); - private static final Setting TRANSPORT_VERIFICATION_MODE_SETTING = - new Setting<>("xpack.security.transport.ssl.verification_mode", VERIFICATION_MODE_DEFAULT.name(), VerificationMode::parse, - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_KEYSTORE_PATH_SETTING = - new Setting<>("xpack.security.transport.ssl.keystore.path", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_KEYSTORE_PASSWORD_SETTING = - new Setting<>("xpack.security.transport.ssl.keystore.password", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting TRANSPORT_KEYSTORE_ALGORITHM_SETTING = - new Setting<>("xpack.security.transport.ssl.keystore.algorithm", "", Function.identity(), Property.NodeScope, - Property.Filtered); - private static final Setting> TRANSPORT_KEYSTORE_KEY_PASSWORD_SETTING = - new Setting<>("xpack.security.transport.ssl.keystore.key_password", TRANSPORT_KEYSTORE_PASSWORD_SETTING, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_TRUSTSTORE_PATH_SETTING = - new Setting<>("xpack.security.transport.ssl.truststore.path", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_TRUSTSTORE_PASSWORD_SETTING = - new Setting<>("xpack.security.transport.ssl.truststore.password", (String) null, Optional::ofNullable, - Property.NodeScope, Property.Filtered); - private static final Setting TRANSPORT_TRUSTSTORE_ALGORITHM_SETTING = - new Setting<>("xpack.security.transport.ssl.truststore.algorithm", "", Function.identity(), - Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_KEY_PATH_SETTING = - new Setting<>("xpack.security.transport.ssl.key", (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_KEY_PASSWORD_SETTING = - new Setting<>("xpack.security.transport.ssl.key_passphrase", (String) null, Optional::ofNullable, Property.NodeScope, - Property.Filtered); - private static final Setting> TRANSPORT_CERT_SETTING = new Setting<>("xpack.security.transport.ssl.certificate", - (String) null, Optional::ofNullable, Property.NodeScope, Property.Filtered); - private static final Setting> TRANSPORT_CA_PATHS_SETTING = - Setting.listSetting("xpack.security.transport.ssl.certificate_authorities", emptyList(), s -> s, - Property.NodeScope, Property.Filtered); + public static final String TRANSPORT_SSL_PREFIX = Security.setting("transport.ssl."); + private static final SSLConfigurationSettings TRANSPORT_SSL = SSLConfigurationSettings.withPrefix(TRANSPORT_SSL_PREFIX); + /* End SSL settings */ static { - ALL_SETTINGS.add(CIPHERS_SETTING); - ALL_SETTINGS.add(SUPPORTED_PROTOCOLS_SETTING); - ALL_SETTINGS.add(KEYSTORE_PATH_SETTING); - ALL_SETTINGS.add(KEYSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(KEYSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(KEYSTORE_KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(KEY_PATH_SETTING); - ALL_SETTINGS.add(KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(CERT_SETTING); - ALL_SETTINGS.add(TRUSTSTORE_PATH_SETTING); - ALL_SETTINGS.add(TRUSTSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(TRUSTSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(CA_PATHS_SETTING); - ALL_SETTINGS.add(VERIFICATION_MODE_SETTING); - ALL_SETTINGS.add(CLIENT_AUTH_SETTING); - ALL_SETTINGS.add(HTTP_CIPHERS_SETTING); - ALL_SETTINGS.add(HTTP_SUPPORTED_PROTOCOLS_SETTING); - ALL_SETTINGS.add(HTTP_KEYSTORE_PATH_SETTING); - ALL_SETTINGS.add(HTTP_KEYSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(HTTP_KEYSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(HTTP_KEYSTORE_KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(HTTP_KEY_PATH_SETTING); - ALL_SETTINGS.add(HTTP_KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(HTTP_CERT_SETTING); - ALL_SETTINGS.add(HTTP_TRUSTSTORE_PATH_SETTING); - ALL_SETTINGS.add(HTTP_TRUSTSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(HTTP_TRUSTSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(HTTP_CA_PATHS_SETTING); - ALL_SETTINGS.add(HTTP_VERIFICATION_MODE_SETTING); - ALL_SETTINGS.add(HTTP_CLIENT_AUTH_SETTING); - ALL_SETTINGS.add(TRANSPORT_CIPHERS_SETTING); - ALL_SETTINGS.add(TRANSPORT_SUPPORTED_PROTOCOLS_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEYSTORE_PATH_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEYSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEYSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEYSTORE_KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEY_PATH_SETTING); - ALL_SETTINGS.add(TRANSPORT_KEY_PASSWORD_SETTING); - ALL_SETTINGS.add(TRANSPORT_CERT_SETTING); - ALL_SETTINGS.add(TRANSPORT_TRUSTSTORE_PATH_SETTING); - ALL_SETTINGS.add(TRANSPORT_TRUSTSTORE_PASSWORD_SETTING); - ALL_SETTINGS.add(TRANSPORT_TRUSTSTORE_ALGORITHM_SETTING); - ALL_SETTINGS.add(TRANSPORT_CA_PATHS_SETTING); - ALL_SETTINGS.add(TRANSPORT_VERIFICATION_MODE_SETTING); - ALL_SETTINGS.add(TRANSPORT_CLIENT_AUTH_SETTING); + ALL_SETTINGS.addAll(GLOBAL_SSL.getAllSettings()); + ALL_SETTINGS.addAll(HTTP_SSL.getAllSettings()); + ALL_SETTINGS.addAll(TRANSPORT_SSL.getAllSettings()); } /** diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpClient.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpClient.java index 368729ecf64..69289d01ce6 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpClient.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpClient.java @@ -39,12 +39,6 @@ import java.util.Map; */ public class HttpClient extends AbstractComponent { - static final String SETTINGS_SSL_PREFIX = "xpack.http.ssl."; - static final String SETTINGS_PROXY_PREFIX = "xpack.http.proxy."; - - static final String SETTINGS_PROXY_HOST = SETTINGS_PROXY_PREFIX + "host"; - static final String SETTINGS_PROXY_PORT = SETTINGS_PROXY_PREFIX + "port"; - private final HttpAuthRegistry httpAuthRegistry; private final TimeValue defaultConnectionTimeout; private final TimeValue defaultReadTimeout; @@ -55,21 +49,27 @@ public class HttpClient extends AbstractComponent { public HttpClient(Settings settings, HttpAuthRegistry httpAuthRegistry, SSLService sslService) { super(settings); this.httpAuthRegistry = httpAuthRegistry; - this.defaultConnectionTimeout = settings.getAsTime("xpack.http.default_connection_timeout", TimeValue.timeValueSeconds(10)); - this.defaultReadTimeout = settings.getAsTime("xpack.http.default_read_timeout", TimeValue.timeValueSeconds(10)); - Integer proxyPort = settings.getAsInt(SETTINGS_PROXY_PORT, null); - String proxyHost = settings.get(SETTINGS_PROXY_HOST, null); + this.defaultConnectionTimeout = HttpSettings.CONNECTION_TIMEOUT.get(settings); + this.defaultReadTimeout = HttpSettings.READ_TIMEOUT.get(settings); + + final Integer proxyPort; + if (HttpSettings.PROXY_HOST.exists(settings)) { + proxyPort = HttpSettings.PROXY_PORT.get(settings); + } else { + proxyPort = null; + } + final String proxyHost = HttpSettings.PROXY_HOST.get(settings); if (proxyPort != null && Strings.hasText(proxyHost)) { this.proxy = new HttpProxy(proxyHost, proxyPort); logger.info("Using default proxy for http input and slack/hipchat/pagerduty/webhook actions [{}:{}]", proxyHost, proxyPort); } else if (proxyPort == null && Strings.hasText(proxyHost) == false) { this.proxy = HttpProxy.NO_PROXY; } else { - throw new IllegalArgumentException("HTTP Proxy requires both settings: [" + SETTINGS_PROXY_HOST + "] and [" + - SETTINGS_PROXY_PORT + "]"); + throw new IllegalArgumentException("HTTP Proxy requires both settings: [" + HttpSettings.PROXY_HOST_KEY + "] and [" + + HttpSettings.PROXY_PORT_KEY + "]"); } - Settings sslSettings = settings.getByPrefix(SETTINGS_SSL_PREFIX); - this.sslSocketFactory = sslService.sslSocketFactory(settings.getByPrefix(SETTINGS_SSL_PREFIX)); + Settings sslSettings = settings.getByPrefix(HttpSettings.SSL_KEY_PREFIX); + this.sslSocketFactory = sslService.sslSocketFactory(settings.getByPrefix(HttpSettings.SSL_KEY_PREFIX)); this.isHostnameVerificationEnabled = sslService.getVerificationMode(sslSettings, Settings.EMPTY).isHostnameVerificationEnabled(); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpSettings.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpSettings.java new file mode 100644 index 00000000000..07eaf5d1e3f --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/common/http/HttpSettings.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.common.http; + +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.ssl.SSLConfigurationSettings; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles the configuration and parsing of settings for the xpack.http. prefix + */ +public class HttpSettings { + + private static final TimeValue DEFAULT_READ_TIMEOUT = TimeValue.timeValueSeconds(10); + private static final TimeValue DEFAULT_CONNECTION_TIMEOUT = DEFAULT_READ_TIMEOUT; + + static final Setting READ_TIMEOUT = Setting.timeSetting("xpack.http.default_read_timeout", + DEFAULT_READ_TIMEOUT, Setting.Property.NodeScope); + static final Setting CONNECTION_TIMEOUT = Setting.timeSetting("xpack.http.default_connection_timeout", + DEFAULT_CONNECTION_TIMEOUT, Setting.Property.NodeScope); + + static final String PROXY_HOST_KEY = "xpack.http.proxy.host"; + static final String PROXY_PORT_KEY = "xpack.http.proxy.port"; + static final String SSL_KEY_PREFIX = "xpack.http.ssl."; + + static final Setting PROXY_HOST = Setting.simpleString(PROXY_HOST_KEY, Setting.Property.NodeScope); + static final Setting PROXY_PORT = Setting.intSetting(PROXY_PORT_KEY, 0, 0, 0xFFFF, Setting.Property.NodeScope); + + private static final SSLConfigurationSettings SSL = SSLConfigurationSettings.withPrefix(SSL_KEY_PREFIX); + + public static List> getSettings() { + final ArrayList> settings = new ArrayList<>(); + settings.addAll(SSL.getAllSettings()); + settings.add(READ_TIMEOUT); + settings.add(CONNECTION_TIMEOUT); + settings.add(PROXY_HOST); + settings.add(PROXY_PORT); + return settings; + } + + private HttpSettings() { + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/extensions/XPackExtension.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/extensions/XPackExtension.java index 9682df1885e..ff83d1aa845 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/extensions/XPackExtension.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/extensions/XPackExtension.java @@ -9,10 +9,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.security.authc.Realm; +import org.elasticsearch.xpack.security.authc.RealmConfig; /** @@ -49,6 +52,16 @@ public abstract class XPackExtension { return Collections.emptyMap(); } + /** + * Returns the set of {@link Setting settings} that may be configured for the each type of realm. + * + * Each setting key must be unqualified and is in the same format as will be provided via {@link RealmConfig#settings()}. + * If a given realm-type is not present in the returned map, then it will be treated as if it supported all possible settings. + * + * The life-cycle of an extension dictates that this method will be called before {@link #getRealms(ResourceWatcherService)} + */ + public Map>> getRealmSettings() { return Collections.emptyMap(); } + /** * Returns a handler for authentication failures, or null to use the default handler. * diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java index 23d1c2df023..15c0fb8065a 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Booleans; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.util.Providers; @@ -47,6 +48,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.extensions.XPackExtension; +import org.elasticsearch.xpack.extensions.XPackExtensionsService; import org.elasticsearch.xpack.security.action.SecurityActionModule; import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter; import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction; @@ -79,15 +81,14 @@ import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authc.InternalRealms; import org.elasticsearch.xpack.security.authc.Realm; +import org.elasticsearch.xpack.security.authc.RealmSettings; import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.authz.AuthorizationService; @@ -246,13 +247,7 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { final AnonymousUser anonymousUser = new AnonymousUser(settings); final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore, anonymousUser); Map realmFactories = new HashMap<>(); - realmFactories.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService)); - realmFactories.put(NativeRealm.TYPE, config -> new NativeRealm(config, nativeUsersStore)); - realmFactories.put(LdapRealm.AD_TYPE, - config -> new LdapRealm(LdapRealm.AD_TYPE, config, resourceWatcherService, sslService, threadPool)); - realmFactories.put(LdapRealm.LDAP_TYPE, - config -> new LdapRealm(LdapRealm.LDAP_TYPE, config, resourceWatcherService, sslService, threadPool)); - realmFactories.put(PkiRealm.TYPE, config -> new PkiRealm(config, resourceWatcherService, sslService)); + realmFactories.putAll(InternalRealms.getFactories(threadPool, resourceWatcherService, sslService, nativeUsersStore)); for (XPackExtension extension : extensions) { Map newRealms = extension.getRealms(resourceWatcherService); for (Map.Entry entry : newRealms.entrySet()) { @@ -380,7 +375,10 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { return settingsBuilder.build(); } - public static List> getSettings(boolean transportClientMode) { + /** + * Get the {@link Setting setting configuration} for all security components, including those defined in extensions. + */ + public static List> getSettings(boolean transportClientMode, @Nullable XPackExtensionsService extensionsService) { List> settingsList = new ArrayList<>(); // always register for both client and node modes settingsList.add(USER_SETTING); @@ -401,7 +399,7 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { // authentication settings AnonymousUser.addSettings(settingsList); - Realms.addSettings(settingsList); + RealmSettings.addSettings(settingsList, extensionsService == null ? null : extensionsService.getExtensions()); NativeRolesStore.addSettings(settingsList); AuthenticationService.addSettings(settingsList); AuthorizationService.addSettings(settingsList); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java new file mode 100644 index 00000000000..8eb29cde111 --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authc; + +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; +import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; +import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.file.FileRealm; +import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; +import org.elasticsearch.xpack.security.authc.pki.PkiRealm; +import org.elasticsearch.xpack.ssl.SSLService; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides a single entry point into dealing with all standard XPack security {@link Realm realms}. + * This class does not handle extensions. + * @see Realms for the component that manages configured realms (including custom extension realms) + */ +public class InternalRealms { + + /** + * The list of all internal realm types, excluding {@link ReservedRealm#TYPE}. + */ + private static final Set TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + NativeRealm.TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, LdapRealm.LDAP_TYPE, PkiRealm.TYPE + ))); + + /** + * Determines whether type is an internal realm-type, optionally considering + * the {@link ReservedRealm}. + */ + public static boolean isInternalRealm(String type, boolean includeReservedRealm) { + if (TYPES.contains(type)) { + return true; + } + if (includeReservedRealm && ReservedRealm.TYPE.equals(type)) { + return true; + } + return false; + } + + /** + * Creates {@link Realm.Factory factories} for each internal realm type. + * This excludes the {@link ReservedRealm}, as it cannot be created dynamically. + * @return A map from realm-type to Factory + */ + public static Map getFactories(ThreadPool threadPool, ResourceWatcherService resourceWatcherService, + SSLService sslService, NativeUsersStore nativeUsersStore){ + Map map = new HashMap<>(); + map.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService)); + map.put(NativeRealm.TYPE, config -> new NativeRealm(config, nativeUsersStore)); + map.put(LdapRealm.AD_TYPE, + config -> new LdapRealm(LdapRealm.AD_TYPE, config, resourceWatcherService, sslService, threadPool)); + map.put(LdapRealm.LDAP_TYPE, + config -> new LdapRealm(LdapRealm.LDAP_TYPE, config, resourceWatcherService, sslService, threadPool)); + map.put(PkiRealm.TYPE, config -> new PkiRealm(config, resourceWatcherService, sslService)); + return Collections.unmodifiableMap(map); + } + + /** + * Provides the {@link Setting setting configuration} for each internal realm type. + * This excludes the {@link ReservedRealm}, as it cannot be configured dynamically. + * @return A map from realm-type to a collection of Setting objects. + */ + public static Map>> getSettings() { + Map>> map = new HashMap<>(); + map.put(FileRealm.TYPE, FileRealm.getSettings()); + map.put(NativeRealm.TYPE, NativeRealm.getSettings()); + map.put(LdapRealm.AD_TYPE, LdapRealm.getSettings(LdapRealm.AD_TYPE)); + map.put(LdapRealm.LDAP_TYPE, LdapRealm.getSettings(LdapRealm.LDAP_TYPE)); + map.put(PkiRealm.TYPE, PkiRealm.getSettings()); + return Collections.unmodifiableMap(map); + } + + private InternalRealms() { + } + +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmConfig.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmConfig.java index cd9d277b04f..0143b7673db 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmConfig.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmConfig.java @@ -29,8 +29,8 @@ public class RealmConfig { this.settings = settings; this.globalSettings = globalSettings; this.env = env; - enabled = settings.getAsBoolean("enabled", true); - order = settings.getAsInt("order", Integer.MAX_VALUE); + enabled = RealmSettings.ENABLED_SETTING.get(settings); + order = RealmSettings.ORDER_SETTING.get(settings); } public String name() { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmSettings.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmSettings.java new file mode 100644 index 00000000000..55d5b125e56 --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/RealmSettings.java @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authc; + +import org.elasticsearch.common.settings.AbstractScopedSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.extensions.XPackExtension; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import static org.elasticsearch.common.Strings.isNullOrEmpty; +import static org.elasticsearch.xpack.security.Security.setting; + +/** + * Configures the {@link Setting#groupSetting(String, Consumer, Setting.Property...) group setting} for security + * {@link Realm realms}, with validation according to the realm type. + *

+ * The allowable settings for a given realm are dependent on the {@link Realm#type() realm type}, so it is not possible + * to simply provide a list of {@link Setting} objects and rely on the global setting validation (e.g. A custom realm-type might + * define a setting with the same logical key as an internal realm-type, but a different data type). + *

+ * Instead, realm configuration relies on the validator parameter to + * {@link Setting#groupSetting(String, Consumer, Setting.Property...)} in order to validate each realm in a way that respects the + * declared type. + * Internally, this validation delegates to {@link AbstractScopedSettings#validate(Settings)} so that validation is reasonably aligned + * with the way we validate settings globally. + *

+ *

+ * The allowable settings for each realm-type are determined by calls to {@link InternalRealms#getSettings()} and + * {@link XPackExtension#getRealmSettings()} + */ +public class RealmSettings { + + public static final String PREFIX = setting("authc.realms."); + + static final Setting TYPE_SETTING = Setting.simpleString("type", Setting.Property.NodeScope); + static final Setting ENABLED_SETTING = Setting.boolSetting("enabled", true, Setting.Property.NodeScope); + static final Setting ORDER_SETTING = Setting.intSetting("order", Integer.MAX_VALUE, Setting.Property.NodeScope); + + /** + * Add the {@link Setting} configuration for all realms to the provided list. + */ + public static void addSettings(List> settingsList, List extensions) { + settingsList.add(getGroupSetting(extensions)); + } + + /** + * Extract the child {@link Settings} for the {@link #PREFIX realms prefix}. + * The top level names in the returned Settings will be the names of the configured realms. + */ + public static Settings get(Settings settings) { + return settings.getByPrefix(RealmSettings.PREFIX); + } + + /** + * Convert the child {@link Setting} for the provided realm into a fully scoped key for use in an error message. + * @see #PREFIX + */ + public static String getFullSettingKey(RealmConfig realm, Setting setting) { + return getFullSettingKey(realm.name(), setting); + } + + /** + * @see #getFullSettingKey(RealmConfig, Setting) + */ + public static String getFullSettingKey(RealmConfig realm, String subKey) { + return getFullSettingKey(realm.name(), subKey); + } + + private static String getFullSettingKey(String name, Setting setting) { + return getFullSettingKey(name, setting.getKey()); + } + + private static String getFullSettingKey(String name, String subKey) { + return PREFIX + name + "." + subKey; + } + + private static Setting getGroupSetting(List extensions) { + return Setting.groupSetting(PREFIX, getSettingsValidator(extensions), Setting.Property.NodeScope); + } + + private static Consumer getSettingsValidator(List extensions) { + final Map>> childSettings = new HashMap<>(InternalRealms.getSettings()); + if (extensions != null) { + extensions.forEach(ext -> { + final Map>> extSettings = ext.getRealmSettings(); + extSettings.keySet().stream().filter(childSettings::containsKey).forEach(type -> { + throw new IllegalArgumentException("duplicate realm type " + type); + }); + childSettings.putAll(extSettings); + }); + } + childSettings.forEach(RealmSettings::verify); + return validator(childSettings); + } + + private static void verify(String type, Set> settings) { + Set keys = new HashSet<>(); + settings.forEach(setting -> { + final String key = setting.getKey(); + if (keys.contains(key)) { + throw new IllegalArgumentException("duplicate setting for key " + key + " in realm type " + type); + } + keys.add(key); + if (setting.getProperties().contains(Setting.Property.NodeScope) == false) { + throw new IllegalArgumentException("setting " + key + " in realm type " + type + " does not have NodeScope"); + } + }); + } + + private static Consumer validator(Map>> validSettings) { + return (settings) -> settings.names().forEach(n -> validateRealm(n, settings.getAsSettings(n), validSettings)); + } + + private static void validateRealm(String name, Settings settings, Map>> validSettings) { + final String type = TYPE_SETTING.get(settings); + if (isNullOrEmpty(type)) { + throw new IllegalArgumentException("missing realm type [" + getFullSettingKey(name, TYPE_SETTING) + "] for realm"); + } + validateRealm(name, type, settings, validSettings.get(type)); + } + + private static void validateRealm(String name, String type, Settings settings, Set> validSettings) { + if (validSettings == null) { + // For backwards compatibility, we assume that is we don't know the valid settings for a realm.type then everything + // is valid. Ideally we would reject these, but XPackExtension doesn't enforce that realm-factories and realm-settings are + // perfectly aligned + return; + } + Set> settingSet = new HashSet<>(validSettings); + settingSet.add(TYPE_SETTING); + settingSet.add(ENABLED_SETTING); + settingSet.add(ORDER_SETTING); + final AbstractScopedSettings validator = new AbstractScopedSettings(settings, settingSet, Setting.Property.NodeScope) { }; + try { + validator.validate(settings); + } catch (RuntimeException e) { + throw new IllegalArgumentException("incorrect configuration for realm [" + getFullSettingKey(name, "") + + "] of type " + type, e); + } + } + + private RealmSettings() { + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java index 5f84a952323..ee111939056 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java @@ -18,8 +18,6 @@ import java.util.Set; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.license.XPackLicenseState; @@ -27,21 +25,13 @@ import org.elasticsearch.license.XPackLicenseState.AllowedRealmType; import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import static org.elasticsearch.xpack.security.Security.setting; /** * Serves as a realms registry (also responsible for ordering the realms appropriately) */ public class Realms extends AbstractComponent implements Iterable { - static final List INTERNAL_REALM_TYPES = - Arrays.asList(ReservedRealm.TYPE, NativeRealm.TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, LdapRealm.LDAP_TYPE, PkiRealm.TYPE); - - public static final Setting REALMS_GROUPS_SETTINGS = Setting.groupSetting(setting("authc.realms."), Property.NodeScope); - private final Environment env; private final Map factories; private final XPackLicenseState licenseState; @@ -68,7 +58,7 @@ public class Realms extends AbstractComponent implements Iterable { List nativeRealms = new ArrayList<>(); for (Realm realm : realms) { // don't add the reserved realm here otherwise we end up with only this realm... - if (INTERNAL_REALM_TYPES.contains(realm.type()) && ReservedRealm.TYPE.equals(realm.type()) == false) { + if (InternalRealms.isInternalRealm(realm.type(), false)) { internalRealms.add(realm); } @@ -142,7 +132,7 @@ public class Realms extends AbstractComponent implements Iterable { } protected List initRealms() throws Exception { - Settings realmsSettings = REALMS_GROUPS_SETTINGS.get(settings); + Settings realmsSettings = RealmSettings.get(settings); Set internalTypes = new HashSet<>(); List realms = new ArrayList<>(); for (String name : realmsSettings.names()) { @@ -239,10 +229,6 @@ public class Realms extends AbstractComponent implements Iterable { } } - public static void addSettings(List> settingsModule) { - settingsModule.add(REALMS_GROUPS_SETTINGS); - } - private static void combineMaps(Map mapA, Map mapB) { for (Entry entry : mapB.entrySet()) { mapA.compute(entry.getKey(), (key, value) -> { @@ -274,9 +260,10 @@ public class Realms extends AbstractComponent implements Iterable { case NATIVE: return FileRealm.TYPE.equals(type) || NativeRealm.TYPE.equals(type); case DEFAULT: - return INTERNAL_REALM_TYPES.contains(type); + return InternalRealms.isInternalRealm(type, true); default: throw new IllegalStateException("unknown enabled realm type [" + enabledRealmType + "]"); } } + } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java index 08d9bf8bca8..e35a440b8b4 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java @@ -6,11 +6,15 @@ package org.elasticsearch.xpack.security.authc.esnative; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.user.User; +import java.util.HashSet; +import java.util.Set; + /** * User/password realm that is backed by an Elasticsearch index */ @@ -34,4 +38,11 @@ public class NativeRealm extends CachingUsernamePasswordRealm { protected void doAuthenticate(UsernamePasswordToken token, ActionListener listener) { userStore.verifyPassword(token.principal(), token.credentials(), listener); } + + /** + * @return The {@link Setting setting configuration} for this realm type + */ + public static Set> getSettings() { + return CachingUsernamePasswordRealm.getCachingSettings(); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java index 2d02d01d3e9..24664cf9af1 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java @@ -5,9 +5,12 @@ */ package org.elasticsearch.xpack.security.authc.file; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; @@ -61,4 +64,11 @@ public class FileRealm extends CachingUsernamePasswordRealm { stats.put("size", userPasswdStore.usersCount()); return stats; } + + /** + * @return The {@link Setting setting configuration} for this realm type + */ + public static Set> getSettings() { + return CachingUsernamePasswordRealm.getCachingSettings(); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java index 85d31e288eb..62617f73a08 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectorySessionFactory.java @@ -16,6 +16,7 @@ import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.RealmConfig; @@ -26,8 +27,10 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.ssl.SSLService; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ExecutionException; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; @@ -107,6 +110,18 @@ class ActiveDirectorySessionFactory extends SessionFactory { return "DC=" + domain.replace(".", ",DC="); } + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.addAll(SessionFactory.getSettings()); + settings.add(Setting.simpleString(AD_DOMAIN_NAME_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_GROUP_SEARCH_BASEDN_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_GROUP_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_USER_SEARCH_BASEDN_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_USER_SEARCH_FILTER_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(AD_USER_SEARCH_SCOPE_SETTING, Setting.Property.NodeScope)); + return settings; + } + ADAuthenticator getADAuthenticator(String username) { if (username.indexOf('\\') > 0) { return downLevelADAuthenticator; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index bc68bc6ac1c..876097da4c5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -13,12 +14,13 @@ import com.unboundid.ldap.sdk.LDAPException; import org.apache.lucene.util.IOUtils; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapLoadBalancing; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; +import org.elasticsearch.xpack.security.authc.RealmSettings; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; @@ -59,13 +61,27 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { sessionFactory = new ActiveDirectorySessionFactory(config, sslService); } else { assert LDAP_TYPE.equals(type) : "type [" + type + "] is unknown. expected one of [" + AD_TYPE + ", " + LDAP_TYPE + "]"; - Settings searchSettings = userSearchSettings(config); - if (searchSettings.names().isEmpty()) { - sessionFactory = new LdapSessionFactory(config, sslService); - } else if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { - throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + - "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + + final boolean hasSearchSettings = LdapUserSearchSessionFactory.hasUserSearchSettings(config); + final boolean hasTemplates = LdapSessionFactory.USER_DN_TEMPLATES_SETTING.exists(config.settings()); + if (hasSearchSettings == false) { + if(hasTemplates == false) { + throw new IllegalArgumentException("settings were not found for either user search [" + + RealmSettings.getFullSettingKey(config, LdapUserSearchSessionFactory.SEARCH_PREFIX) + + "] or user template [" + + RealmSettings.getFullSettingKey(config, LdapSessionFactory.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); + } else if (hasTemplates) { + throw new IllegalArgumentException("settings were found for both user search [" + + RealmSettings.getFullSettingKey(config, LdapUserSearchSessionFactory.SEARCH_PREFIX) + + "] and user template [" + + RealmSettings.getFullSettingKey(config, LdapSessionFactory.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); } @@ -73,8 +89,22 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { return sessionFactory; } - static Settings userSearchSettings(RealmConfig config) { - return config.settings().getAsSettings("user_search"); + /** + * @return The {@link Setting setting configuration} for this realm type + * @param type Either {@link #AD_TYPE} or {@link #LDAP_TYPE} + */ + public static Set> getSettings(String type) { + Set> settings = new HashSet<>(); + settings.addAll(CachingUsernamePasswordRealm.getCachingSettings()); + DnRoleMapper.getSettings(settings); + if (AD_TYPE.equals(type)) { + settings.addAll(ActiveDirectorySessionFactory.getSettings()); + } else { + assert LDAP_TYPE.equals(type) : "type [" + type + "] is unknown. expected one of [" + AD_TYPE + ", " + LDAP_TYPE + "]"; + settings.addAll(LdapSessionFactory.getSettings()); + settings.addAll(LdapUserSearchSessionFactory.getSettings()); + } + return settings; } /** @@ -106,7 +136,7 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { Map usage = super.usageStats(); usage.put("load_balance_type", LdapLoadBalancing.resolve(config.settings()).toString()); usage.put("ssl", sessionFactory.isSslUsed()); - usage.put("user_search", userSearchSettings(config).isEmpty() == false); + usage.put("user_search", LdapUserSearchSessionFactory.hasUserSearchSettings(config)); return usage; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java index c0471fa51cd..32cf32c4e77 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java @@ -12,8 +12,11 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.apache.lucene.util.IOUtils; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; +import org.elasticsearch.xpack.security.authc.RealmSettings; 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.SessionFactory; @@ -23,7 +26,12 @@ import org.elasticsearch.xpack.ssl.SSLService; import java.text.MessageFormat; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; +import java.util.function.Function; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.escapedRDNValue; @@ -35,7 +43,8 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.esca */ public class LdapSessionFactory extends SessionFactory { - public static final String USER_DN_TEMPLATES_SETTING = "user_dn_templates"; + public static final Setting> USER_DN_TEMPLATES_SETTING = Setting.listSetting("user_dn_templates", + Collections.emptyList(), Function.identity(), Setting.Property.NodeScope); private final String[] userDnTemplates; private final GroupsResolver groupResolver; @@ -43,9 +52,10 @@ public class LdapSessionFactory extends SessionFactory { public LdapSessionFactory(RealmConfig config, SSLService sslService) { super(config, sslService); Settings settings = config.settings(); - userDnTemplates = settings.getAsArray(USER_DN_TEMPLATES_SETTING); - if (userDnTemplates == null) { - throw new IllegalArgumentException("missing required LDAP setting [" + USER_DN_TEMPLATES_SETTING + "]"); + userDnTemplates = USER_DN_TEMPLATES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY); + if (userDnTemplates.length == 0) { + throw new IllegalArgumentException("missing required LDAP setting [" + + RealmSettings.getFullSettingKey(config, USER_DN_TEMPLATES_SETTING) + "]"); } groupResolver = groupResolver(settings); } @@ -116,10 +126,16 @@ public class LdapSessionFactory extends SessionFactory { } static GroupsResolver groupResolver(Settings settings) { - Settings searchSettings = settings.getAsSettings("group_search"); - if (!searchSettings.names().isEmpty()) { - return new SearchGroupsResolver(searchSettings); + if (SearchGroupsResolver.BASE_DN.exists(settings)) { + return new SearchGroupsResolver(settings); } return new UserAttributeGroupsResolver(settings); } + + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.addAll(SessionFactory.getSettings()); + settings.add(USER_DN_TEMPLATES_SETTING); + return settings; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java index 4897eba0817..e8ccb404843 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java @@ -17,9 +17,11 @@ import com.unboundid.ldap.sdk.SimpleBindRequest; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.IOUtils; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.RealmConfig; +import org.elasticsearch.xpack.security.authc.RealmSettings; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; @@ -30,6 +32,11 @@ import org.elasticsearch.xpack.ssl.SSLService; import java.util.Arrays; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + import static com.unboundid.ldap.sdk.Filter.createEqualityFilter; import static com.unboundid.ldap.sdk.Filter.encodeValue; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; @@ -42,6 +49,30 @@ class LdapUserSearchSessionFactory extends SessionFactory { static final String DEFAULT_USERNAME_ATTRIBUTE = "uid"; static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L); + static final String SEARCH_PREFIX = "user_search."; + + private static final Setting SEARCH_BASE_DN = Setting.simpleString("user_search.base_dn", Setting.Property.NodeScope); + private static final Setting SEARCH_ATTRIBUTE = new Setting<>("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE, + Function.identity(), Setting.Property.NodeScope); + private static final Setting SEARCH_SCOPE = new Setting<>("user_search.scope", (String) null, + s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope); + + private static final Setting POOL_ENABLED = Setting.boolSetting("user_search.pool.enabled", + true, Setting.Property.NodeScope); + private static final Setting POOL_INITIAL_SIZE = Setting.intSetting("user_search.pool.initial_size", + DEFAULT_CONNECTION_POOL_INITIAL_SIZE, 0, Setting.Property.NodeScope); + private static final Setting POOL_SIZE = Setting.intSetting("user_search.pool.size", + DEFAULT_CONNECTION_POOL_SIZE, 1, Setting.Property.NodeScope); + private static final Setting HEALTH_CHECK_INTERVAL = Setting.timeSetting("user_search.pool.health_check.interval", + DEFAULT_HEALTH_CHECK_INTERVAL, Setting.Property.NodeScope); + private static final Setting HEALTH_CHECK_ENABLED = Setting.boolSetting("user_search.pool.health_check.enabled", + true, Setting.Property.NodeScope); + private static final Setting> HEALTH_CHECK_DN = new Setting<>("user_search.pool.health_check.dn", (String) null, + Optional::ofNullable, Setting.Property.NodeScope); + + private static final Setting BIND_DN = Setting.simpleString("bind_dn", Setting.Property.NodeScope); + private static final Setting BIND_PASSWORD = Setting.simpleString("bind_password", Setting.Property.NodeScope); + private final String userSearchBaseDn; private final LdapSearchScope scope; private final String userAttribute; @@ -53,14 +84,15 @@ class LdapUserSearchSessionFactory extends SessionFactory { LdapUserSearchSessionFactory(RealmConfig config, SSLService sslService) throws LDAPException { super(config, sslService); Settings settings = config.settings(); - userSearchBaseDn = settings.get("user_search.base_dn"); - if (userSearchBaseDn == null) { - throw new IllegalArgumentException("user_search base_dn must be specified"); + if (SEARCH_BASE_DN.exists(settings)) { + userSearchBaseDn = SEARCH_BASE_DN.get(settings); + } else { + throw new IllegalArgumentException("[" + RealmSettings.getFullSettingKey(config, SEARCH_BASE_DN) + "] must be specified"); } - scope = LdapSearchScope.resolve(settings.get("user_search.scope"), LdapSearchScope.SUB_TREE); - userAttribute = settings.get("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE); - groupResolver = groupResolver(config.settings()); - useConnectionPool = settings.getAsBoolean("user_search.pool.enabled", true); + scope = SEARCH_SCOPE.get(settings); + userAttribute = SEARCH_ATTRIBUTE.get(settings); + groupResolver = groupResolver(settings); + useConnectionPool = POOL_ENABLED.get(settings); if (useConnectionPool) { connectionPool = createConnectionPool(config, serverSet, timeout, logger); } else { @@ -72,17 +104,16 @@ class LdapUserSearchSessionFactory extends SessionFactory { throws LDAPException { Settings settings = config.settings(); SimpleBindRequest bindRequest = bindRequest(settings); - final int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); - final int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + final int initialSize = POOL_INITIAL_SIZE.get(settings); + final int size = POOL_SIZE.get(settings); LDAPConnectionPool pool = null; boolean success = false; try { pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); pool.setRetryFailedOperationsDueToInvalidConnections(true); - if (settings.getAsBoolean("user_search.pool.health_check.enabled", true)) { - String entryDn = settings.get("user_search.pool.health_check.dn", (bindRequest == null) ? null : bindRequest.getBindDN()); - final long healthCheckInterval = - settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL).millis(); + if (HEALTH_CHECK_ENABLED.get(settings)) { + String entryDn = HEALTH_CHECK_DN.get(settings).orElseGet(() -> bindRequest == null ? null : bindRequest.getBindDN()); + final long healthCheckInterval = HEALTH_CHECK_INTERVAL.get(settings).millis(); if (entryDn != null) { // Checks the status of the LDAP connection at a specified interval in the background. We do not check on // on create as the LDAP server may require authentication to get an entry and a bind request has not been executed @@ -93,7 +124,8 @@ class LdapUserSearchSessionFactory extends SessionFactory { pool.setHealthCheck(healthCheck); pool.setHealthCheckIntervalMillis(healthCheckInterval); } else { - logger.warn("[bind_dn] and [user_search.pool.health_check.dn] have not been specified so no " + + logger.warn("[" + RealmSettings.getFullSettingKey(config, BIND_DN) + "] and [" + + RealmSettings.getFullSettingKey(config, HEALTH_CHECK_DN) + "] have not been specified so no " + "ldap query will be run as a health check"); } } @@ -109,13 +141,16 @@ class LdapUserSearchSessionFactory extends SessionFactory { static SimpleBindRequest bindRequest(Settings settings) { SimpleBindRequest request = null; - String bindDn = settings.get("bind_dn"); - if (bindDn != null) { - request = new SimpleBindRequest(bindDn, settings.get("bind_password")); + if (BIND_DN.exists(settings)) { + request = new SimpleBindRequest(BIND_DN.get(settings), BIND_PASSWORD.get(settings)); } return request; } + public static boolean hasUserSearchSettings(RealmConfig config) { + return config.settings().getByPrefix("user_search.").isEmpty() == false; + } + @Override public void session(String user, SecuredString password, ActionListener listener) { if (useConnectionPool) { @@ -268,10 +303,30 @@ class LdapUserSearchSessionFactory extends SessionFactory { } static GroupsResolver groupResolver(Settings settings) { - Settings searchSettings = settings.getAsSettings("group_search"); - if (!searchSettings.names().isEmpty()) { - return new SearchGroupsResolver(searchSettings); + if (SearchGroupsResolver.BASE_DN.exists(settings)) { + return new SearchGroupsResolver(settings); } return new UserAttributeGroupsResolver(settings); } + + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.addAll(SessionFactory.getSettings()); + settings.add(SEARCH_BASE_DN); + settings.add(SEARCH_SCOPE); + settings.add(SEARCH_ATTRIBUTE); + settings.add(POOL_ENABLED); + settings.add(POOL_INITIAL_SIZE); + settings.add(POOL_SIZE); + settings.add(HEALTH_CHECK_ENABLED); + settings.add(HEALTH_CHECK_DN); + settings.add(HEALTH_CHECK_INTERVAL); + settings.add(BIND_DN); + settings.add(BIND_PASSWORD); + + settings.addAll(SearchGroupsResolver.getSettings()); + settings.addAll(UserAttributeGroupsResolver.getSettings()); + + return settings; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java index 66ebb1e9ab3..18c0ceedfd0 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java @@ -13,6 +13,8 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchScope; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -20,9 +22,13 @@ import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsRes import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; +import java.util.Set; +import java.util.function.Function; +import static org.elasticsearch.common.Strings.isNullOrEmpty; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search; @@ -38,19 +44,28 @@ class SearchGroupsResolver implements GroupsResolver { "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" + "(|(uniqueMember={0})(member={0})(memberUid={0})))"; + static final Setting BASE_DN = Setting.simpleString("group_search.base_dn", Setting.Property.NodeScope); + static final Setting SCOPE = new Setting<>("group_search.scope", (String) null, + s -> LdapSearchScope.resolve(s, LdapSearchScope.SUB_TREE), Setting.Property.NodeScope); + static final Setting USER_ATTRIBUTE = Setting.simpleString("group_search.user_attribute", Setting.Property.NodeScope); + + static final Setting FILTER = new Setting<>("group_search.filter", GROUP_SEARCH_DEFAULT_FILTER, + Function.identity(), Setting.Property.NodeScope); + private final String baseDn; private final String filter; private final String userAttribute; private final LdapSearchScope scope; SearchGroupsResolver(Settings settings) { - baseDn = settings.get("base_dn"); - if (baseDn == null) { + if (BASE_DN.exists(settings)) { + baseDn = BASE_DN.get(settings); + } else { throw new IllegalArgumentException("base_dn must be specified"); } - filter = settings.get("filter", GROUP_SEARCH_DEFAULT_FILTER); - userAttribute = settings.get("user_attribute"); - scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE); + filter = FILTER.get(settings); + userAttribute = USER_ATTRIBUTE.get(settings); + scope = SCOPE.get(settings); } @Override @@ -75,7 +90,7 @@ class SearchGroupsResolver implements GroupsResolver { } public String[] attributes() { - if (userAttribute != null) { + if (Strings.hasLength(userAttribute)) { return new String[] { userAttribute }; } return null; @@ -83,7 +98,7 @@ class SearchGroupsResolver implements GroupsResolver { private void getUserId(String dn, Collection attributes, LDAPInterface connection, TimeValue timeout, ActionListener listener) { - if (userAttribute == null) { + if (isNullOrEmpty(userAttribute)) { listener.onResponse(dn); } else if (attributes != null) { final String value = attributes.stream().filter((attribute) -> attribute.getName().equals(userAttribute)) @@ -107,4 +122,13 @@ class SearchGroupsResolver implements GroupsResolver { }, listener::onFailure), userAttribute); } + + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.add(BASE_DN); + settings.add(FILTER); + settings.add(USER_ATTRIBUTE); + settings.add(SCOPE); + return settings; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java index cae48b6fab6..27ac1b57491 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java @@ -10,6 +10,7 @@ import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchScope; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; @@ -20,6 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import java.util.Set; +import java.util.function.Function; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; @@ -29,10 +32,12 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear */ class UserAttributeGroupsResolver implements GroupsResolver { + private static final Setting ATTRIBUTE = new Setting<>("user_group_attribute", "memberOf", + Function.identity(), Setting.Property.NodeScope); private final String attribute; UserAttributeGroupsResolver(Settings settings) { - this(settings.get("user_group_attribute", "memberOf")); + this(ATTRIBUTE.get(settings)); } private UserAttributeGroupsResolver(String attribute) { @@ -62,4 +67,8 @@ class UserAttributeGroupsResolver implements GroupsResolver { public String[] attributes() { return new String[] { attribute }; } + + public static Set> getSettings() { + return Collections.singleton(ATTRIBUTE); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapLoadBalancing.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapLoadBalancing.java index ba8b270790d..40b206b0f66 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapLoadBalancing.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapLoadBalancing.java @@ -12,11 +12,14 @@ import com.unboundid.ldap.sdk.RoundRobinServerSet; import com.unboundid.ldap.sdk.ServerSet; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import javax.net.SocketFactory; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; /** * Enumeration representing the various supported {@link ServerSet} types that can be used with out built in realms. @@ -51,7 +54,7 @@ public enum LdapLoadBalancing { if (InetAddresses.isInetAddress(addresses[0])) { throw new IllegalArgumentException(toString() + " can only be used with a DNS name"); } - TimeValue dnsTtl = settings.getAsTime("cache_ttl", TimeValue.timeValueHours(1L)); + TimeValue dnsTtl = settings.getAsTime(CACHE_TTL_SETTING, CACHE_TTL_DEFAULT); return new RoundRobinDNSServerSet(addresses[0], ports[0], RoundRobinDNSServerSet.AddressSelectionMode.ROUND_ROBIN, dnsTtl.millis(), null, socketFactory, options); } @@ -67,7 +70,7 @@ public enum LdapLoadBalancing { if (InetAddresses.isInetAddress(addresses[0])) { throw new IllegalArgumentException(toString() + " can only be used with a DNS name"); } - TimeValue dnsTtl = settings.getAsTime("cache_ttl", TimeValue.timeValueHours(1L)); + TimeValue dnsTtl = settings.getAsTime(CACHE_TTL_SETTING, CACHE_TTL_DEFAULT); return new RoundRobinDNSServerSet(addresses[0], ports[0], RoundRobinDNSServerSet.AddressSelectionMode.FAILOVER, dnsTtl.millis(), null, socketFactory, options); } @@ -76,6 +79,8 @@ public enum LdapLoadBalancing { public static final String LOAD_BALANCE_SETTINGS = "load_balance"; public static final String LOAD_BALANCE_TYPE_SETTING = "type"; public static final String LOAD_BALANCE_TYPE_DEFAULT = LdapLoadBalancing.FAILOVER.toString(); + public static final String CACHE_TTL_SETTING = "cache_ttl"; + public static final TimeValue CACHE_TTL_DEFAULT = TimeValue.timeValueHours(1L); abstract ServerSet buildServerSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, @Nullable LDAPConnectionOptions options); @@ -101,4 +106,11 @@ public enum LdapLoadBalancing { Settings loadBalanceSettings = settings.getAsSettings(LOAD_BALANCE_SETTINGS); return loadBalancing.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options); } + + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.add(Setting.simpleString(LOAD_BALANCE_SETTINGS + "." + LOAD_BALANCE_TYPE_SETTING, Setting.Property.NodeScope)); + settings.add(Setting.simpleString(LOAD_BALANCE_SETTINGS + "." + CACHE_TTL_SETTING, Setting.Property.NodeScope)); + return settings; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index bb888ba1ec8..557c2d2c7f2 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -13,14 +13,20 @@ import com.unboundid.util.ssl.HostNameSSLSocketVerifier; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.SecuredString; +import org.elasticsearch.xpack.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.ssl.SSLService; import javax.net.SocketFactory; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; import java.util.regex.Pattern; /** @@ -149,6 +155,19 @@ public abstract class SessionFactory { return sslUsed; } + protected static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.addAll(LdapLoadBalancing.getSettings()); + settings.add(Setting.listSetting(URLS_SETTING, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); + settings.add(Setting.timeSetting(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT, Setting.Property.NodeScope)); + settings.add(Setting.boolSetting(HOSTNAME_VERIFICATION_SETTING, true, Setting.Property.NodeScope)); + settings.add(Setting.boolSetting(FOLLOW_REFERRALS_SETTING, true, Setting.Property.NodeScope)); + settings.addAll(SSLConfigurationSettings.withPrefix("ssl.").getAllSettings()); + return settings; + } + public static class LDAPServers { private final String[] addresses; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java index 22142adadec..5adde48ed5e 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java @@ -11,42 +11,48 @@ import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.transport.netty4.SecurityNetty4Transport; import org.elasticsearch.xpack.ssl.CertUtils; +import org.elasticsearch.xpack.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.ssl.SSLService; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.authc.Realm; import org.elasticsearch.xpack.security.authc.RealmConfig; +import org.elasticsearch.xpack.security.authc.RealmSettings; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; -import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.elasticsearch.xpack.security.Security.setting; import static org.elasticsearch.xpack.XPackSettings.HTTP_SSL_ENABLED; import static org.elasticsearch.xpack.XPackSettings.TRANSPORT_SSL_ENABLED; +import static org.elasticsearch.xpack.security.Security.setting; public class PkiRealm extends Realm { public static final String PKI_CERT_HEADER_NAME = "__SECURITY_CLIENT_CERTIFICATE"; public static final String TYPE = "pki"; - public static final String DEFAULT_USERNAME_PATTERN = "CN=(.*?)(?:,|$)"; + + static final String DEFAULT_USERNAME_PATTERN = "CN=(.*?)(?:,|$)"; + private static final Setting USERNAME_PATTERN_SETTING = new Setting<>("username_pattern", DEFAULT_USERNAME_PATTERN, + s -> Pattern.compile(s, Pattern.CASE_INSENSITIVE), Setting.Property.NodeScope); + private static final SSLConfigurationSettings SSL_SETTINGS = SSLConfigurationSettings.withoutPrefix(); // For client based cert validation, the auth type must be specified but UNKNOWN is an acceptable value public static final String AUTH_TYPE = "UNKNOWN"; @@ -64,8 +70,7 @@ public class PkiRealm extends Realm { PkiRealm(RealmConfig config, DnRoleMapper roleMapper, SSLService sslService) { super(TYPE, config); this.trustManager = trustManagers(config); - this.principalPattern = Pattern.compile(config.settings().get("username_pattern", DEFAULT_USERNAME_PATTERN), - Pattern.CASE_INSENSITIVE); + this.principalPattern = USERNAME_PATTERN_SETTING.get(config.settings()); this.roleMapper = roleMapper; checkSSLEnabled(config, sslService); } @@ -149,31 +154,26 @@ public class PkiRealm extends Realm { static X509TrustManager trustManagers(RealmConfig realmConfig) { final Settings settings = realmConfig.settings(); final Environment env = realmConfig.env(); - String[] certificateAuthorities = settings.getAsArray("certificate_authorities", null); - String truststorePath = settings.get("truststore.path"); + String[] certificateAuthorities = settings.getAsArray(SSL_SETTINGS.caPaths.getKey(), null); + String truststorePath = SSL_SETTINGS.truststorePath.get(settings).orElse(null); if (truststorePath == null && certificateAuthorities == null) { return null; } else if (truststorePath != null && certificateAuthorities != null) { - final String settingPrefix = Realms.REALMS_GROUPS_SETTINGS.getKey() + realmConfig.name() + "."; - throw new IllegalArgumentException("[" + settingPrefix + "truststore.path] and [" + settingPrefix + "certificate_authorities]" + - " cannot be used at the same time"); + final String pathKey = RealmSettings.getFullSettingKey(realmConfig, SSL_SETTINGS.truststorePath); + final String caKey = RealmSettings.getFullSettingKey(realmConfig, SSL_SETTINGS.caPaths); + throw new IllegalArgumentException("[" + pathKey + "] and [" + caKey + "] cannot be used at the same time"); } else if (truststorePath != null) { - return trustManagersFromTruststore(realmConfig); + return trustManagersFromTruststore(truststorePath, realmConfig); } return trustManagersFromCAs(settings, env); } - private static X509TrustManager trustManagersFromTruststore(RealmConfig realmConfig) { + private static X509TrustManager trustManagersFromTruststore(String truststorePath, RealmConfig realmConfig) { final Settings settings = realmConfig.settings(); - String truststorePath = settings.get("truststore.path"); - String password = settings.get("truststore.password"); - if (password == null) { - final String settingPrefix = Realms.REALMS_GROUPS_SETTINGS.getKey() + realmConfig.name() + "."; - throw new IllegalArgumentException("[" + settingPrefix + "truststore.password] is not configured"); - } - - String trustStoreAlgorithm = settings.get("truststore.algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm", - TrustManagerFactory.getDefaultAlgorithm())); + String password = SSL_SETTINGS.truststorePassword.get(settings).orElseThrow(() -> new IllegalArgumentException( + "[" + RealmSettings.getFullSettingKey(realmConfig, SSL_SETTINGS.truststorePassword) + "] is not configured" + )); + String trustStoreAlgorithm = SSL_SETTINGS.truststoreAlgorithm.get(settings); try { return CertUtils.trustManager(truststorePath, password, trustStoreAlgorithm, realmConfig.env()); } catch (Exception e) { @@ -182,7 +182,7 @@ public class PkiRealm extends Realm { } private static X509TrustManager trustManagersFromCAs(Settings settings, Environment env) { - String[] certificateAuthorities = settings.getAsArray("certificate_authorities", null); + String[] certificateAuthorities = settings.getAsArray(SSL_SETTINGS.caPaths.getKey(), null); assert certificateAuthorities != null; try { Certificate[] certificates = CertUtils.readCertificates(Arrays.asList(certificateAuthorities), env); @@ -232,4 +232,21 @@ public class PkiRealm extends Realm { throw new IllegalStateException("PKI realm [" + config.name() + "] is enabled but cannot be used as neither HTTP or Transport " + "has SSL with client authentication enabled"); } + + /** + * @return The {@link Setting setting configuration} for this realm type + */ + public static Set> getSettings() { + Set> settings = new HashSet<>(); + settings.add(USERNAME_PATTERN_SETTING); + + settings.add(SSL_SETTINGS.truststorePath); + settings.add(SSL_SETTINGS.truststorePassword); + settings.add(SSL_SETTINGS.truststoreAlgorithm); + settings.add(SSL_SETTINGS.caPaths); + + DnRoleMapper.getSettings(settings); + + return settings; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java index 25cf62bd617..5704c74c590 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java @@ -8,34 +8,41 @@ package org.elasticsearch.xpack.security.authc.support; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.user.User; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm implements CachingRealm { - public static final String CACHE_HASH_ALGO_SETTING = "cache.hash_algo"; - public static final String CACHE_TTL_SETTING = "cache.ttl"; - public static final String CACHE_MAX_USERS_SETTING = "cache.max_users"; + public static final Setting CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", Setting.Property.NodeScope); private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20); - private static final int DEFAULT_MAX_USERS = 100000; //100k users + public static final Setting CACHE_TTL_SETTING = Setting.timeSetting("cache.ttl", DEFAULT_TTL, Setting.Property.NodeScope); + + private static final int DEFAULT_MAX_USERS = 100_000; //100k users + public static final Setting CACHE_MAX_USERS_SETTING = Setting.intSetting("cache.max_users", DEFAULT_MAX_USERS, + Setting.Property.NodeScope); private final Cache cache; final Hasher hasher; protected CachingUsernamePasswordRealm(String type, RealmConfig config) { super(type, config); - hasher = Hasher.resolve(config.settings().get(CACHE_HASH_ALGO_SETTING, null), Hasher.SSHA256); - TimeValue ttl = config.settings().getAsTime(CACHE_TTL_SETTING, DEFAULT_TTL); + hasher = Hasher.resolve(CACHE_HASH_ALGO_SETTING.get(config.settings()), Hasher.SSHA256); + TimeValue ttl = CACHE_TTL_SETTING.get(config.settings()); if (ttl.getNanos() > 0) { cache = CacheBuilder.builder() .setExpireAfterAccess(ttl) - .setMaximumWeight(config.settings().getAsInt(CACHE_MAX_USERS_SETTING, DEFAULT_MAX_USERS)) + .setMaximumWeight(CACHE_MAX_USERS_SETTING.get(config.settings())) .build(); } else { cache = null; @@ -172,6 +179,13 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm protected abstract void doLookupUser(String username, ActionListener listener); + /** + * Returns the {@link Setting setting configuration} that is common for all caching realms + */ + protected static Set> getCachingSettings() { + return new HashSet<>(Arrays.asList(CACHE_HASH_ALGO_SETTING, CACHE_TTL_SETTING, CACHE_MAX_USERS_SETTING)); + } + private static class UserWithHash { User user; char[] hash; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java index c56bf0da6aa..1c072b03227 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java @@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.watcher.FileChangesListener; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; @@ -41,9 +43,12 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.rela */ public class DnRoleMapper { - public static final String DEFAULT_FILE_NAME = "role_mapping.yml"; - public static final String ROLE_MAPPING_FILE_SETTING = "files.role_mapping"; - public static final String USE_UNMAPPED_GROUPS_AS_ROLES_SETTING = "unmapped_groups_as_roles"; + private static final String DEFAULT_FILE_NAME = "role_mapping.yml"; + public static final Setting ROLE_MAPPING_FILE_SETTING = new Setting<>("files.role_mapping", DEFAULT_FILE_NAME, + Function.identity(), Setting.Property.NodeScope); + + public static final Setting USE_UNMAPPED_GROUPS_AS_ROLES_SETTING = Setting.boolSetting("unmapped_groups_as_roles", false, + Setting.Property.NodeScope); protected final Logger logger; protected final RealmConfig config; @@ -61,7 +66,7 @@ public class DnRoleMapper { this.logger = config.logger(getClass()); this.listeners = new CopyOnWriteArrayList<>(Collections.singleton(listener)); - useUnmappedGroupsAsRoles = config.settings().getAsBoolean(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false); + useUnmappedGroupsAsRoles = USE_UNMAPPED_GROUPS_AS_ROLES_SETTING.get(config.settings()); file = resolveFile(config.settings(), config.env()); dnRoles = parseFileLenient(file, logger, realmType, config.name()); FileWatcher watcher = new FileWatcher(file.getParent()); @@ -78,7 +83,7 @@ public class DnRoleMapper { } public static Path resolveFile(Settings settings, Environment env) { - String location = settings.get(ROLE_MAPPING_FILE_SETTING, DEFAULT_FILE_NAME); + String location = ROLE_MAPPING_FILE_SETTING.get(settings); return XPackPlugin.resolveConfigFile(env, location); } @@ -185,6 +190,11 @@ public class DnRoleMapper { listeners.forEach(Runnable::run); } + public static void getSettings(Set> settings) { + settings.add(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING); + settings.add(ROLE_MAPPING_FILE_SETTING); + } + private class FileListener implements FileChangesListener { @Override public void onFileCreated(Path file) { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfiguration.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfiguration.java index db5c2f8ba40..b99115a32d5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfiguration.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfiguration.java @@ -5,22 +5,19 @@ */ package org.elasticsearch.xpack.ssl; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.TrustManagerFactory; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.xpack.XPackSettings; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + /** * Represents the configuration for an SSLContext */ @@ -29,48 +26,7 @@ class SSLConfiguration { // These settings are never registered, but they exist so that we can parse the values defined under grouped settings. Also, some are // implemented as optional settings, which provides a declarative manner for fallback as we typically fallback to values from a // different configuration - private static final Setting> CIPHERS_SETTING = Setting.listSetting("cipher_suites", Collections.emptyList(), s -> s); - private static final Setting> SUPPORTED_PROTOCOLS_SETTING = - Setting.listSetting("supported_protocols", Collections.emptyList(), s -> s); - private static final Setting> KEYSTORE_PATH_SETTING = - new Setting<>("keystore.path", (String) null, Optional::ofNullable); - private static final Setting> KEYSTORE_PASSWORD_SETTING = - new Setting<>("keystore.password", (String) null, Optional::ofNullable); - private static final Setting KEYSTORE_ALGORITHM_SETTING = new Setting<>("keystore.algorithm", - s -> System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()), Function.identity()); - private static final Setting> KEYSTORE_KEY_PASSWORD_SETTING = - new Setting<>("keystore.key_password", KEYSTORE_PASSWORD_SETTING, Optional::ofNullable); - private static final Setting> TRUSTSTORE_PATH_SETTING = - new Setting<>("truststore.path", (String) null, Optional::ofNullable); - private static final Setting> TRUSTSTORE_PASSWORD_SETTING = - new Setting<>("truststore.password", (String) null, Optional::ofNullable); - private static final Setting TRUSTSTORE_ALGORITHM_SETTING = new Setting<>("truststore.algorithm", - s -> System.getProperty("ssl.TrustManagerFactory.algorithm", - TrustManagerFactory.getDefaultAlgorithm()), Function.identity()); - private static final Setting> KEY_PATH_SETTING = - new Setting<>("key", (String) null, Optional::ofNullable); - private static final Setting> KEY_PASSWORD_SETTING = - new Setting<>("key_passphrase", (String) null, Optional::ofNullable); - private static final Setting> CERT_SETTING = - new Setting<>("certificate", (String) null, Optional::ofNullable); - private static final Setting> CA_PATHS_SETTING = - Setting.listSetting("certificate_authorities", Collections.emptyList(), s -> s); - private static final Setting> CLIENT_AUTH_SETTING = - new Setting<>("client_authentication", (String) null, s -> { - if (s == null) { - return Optional.ofNullable(null); - } else { - return Optional.of(SSLClientAuth.parse(s)); - } - }); - private static final Setting> VERIFICATION_MODE_SETTING = new Setting<>("verification_mode", (String) null, - s -> { - if (s == null) { - return Optional.ofNullable(null); - } else { - return Optional.of(VerificationMode.parse(s)); - } - }); + private static final SSLConfigurationSettings SETTINGS_PARSER = SSLConfigurationSettings.withoutPrefix(); private final KeyConfig keyConfig; private final TrustConfig trustConfig; @@ -87,10 +43,10 @@ class SSLConfiguration { SSLConfiguration(Settings settings) { this.keyConfig = createKeyConfig(settings, null); this.trustConfig = createTrustConfig(settings, keyConfig, null); - this.ciphers = getListOrDefault(CIPHERS_SETTING, settings, XPackSettings.DEFAULT_CIPHERS); - this.supportedProtocols = getListOrDefault(SUPPORTED_PROTOCOLS_SETTING, settings, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS); - this.sslClientAuth = CLIENT_AUTH_SETTING.get(settings).orElse(XPackSettings.CLIENT_AUTH_DEFAULT); - this.verificationMode = VERIFICATION_MODE_SETTING.get(settings).orElse(XPackSettings.VERIFICATION_MODE_DEFAULT); + this.ciphers = getListOrDefault(SETTINGS_PARSER.ciphers, settings, XPackSettings.DEFAULT_CIPHERS); + this.supportedProtocols = getListOrDefault(SETTINGS_PARSER.supportedProtocols, settings, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS); + this.sslClientAuth = SETTINGS_PARSER.clientAuth.get(settings).orElse(XPackSettings.CLIENT_AUTH_DEFAULT); + this.verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElse(XPackSettings.VERIFICATION_MODE_DEFAULT); } /** @@ -103,10 +59,11 @@ class SSLConfiguration { Objects.requireNonNull(globalSSLConfiguration); this.keyConfig = createKeyConfig(settings, globalSSLConfiguration); this.trustConfig = createTrustConfig(settings, keyConfig, globalSSLConfiguration); - this.ciphers = getListOrDefault(CIPHERS_SETTING, settings, globalSSLConfiguration.cipherSuites()); - this.supportedProtocols = getListOrDefault(SUPPORTED_PROTOCOLS_SETTING, settings, globalSSLConfiguration.supportedProtocols()); - this.sslClientAuth = CLIENT_AUTH_SETTING.get(settings).orElse(globalSSLConfiguration.sslClientAuth()); - this.verificationMode = VERIFICATION_MODE_SETTING.get(settings).orElse(globalSSLConfiguration.verificationMode()); + this.ciphers = getListOrDefault(SETTINGS_PARSER.ciphers, settings, globalSSLConfiguration.cipherSuites()); + this.supportedProtocols = getListOrDefault(SETTINGS_PARSER.supportedProtocols, settings, + globalSSLConfiguration.supportedProtocols()); + this.sslClientAuth = SETTINGS_PARSER.clientAuth.get(settings).orElse(globalSSLConfiguration.sslClientAuth()); + this.verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElse(globalSSLConfiguration.verificationMode()); } /** @@ -216,8 +173,8 @@ class SSLConfiguration { } private static KeyConfig createKeyConfig(Settings settings, SSLConfiguration global) { - String keyStorePath = KEYSTORE_PATH_SETTING.get(settings).orElse(null); - String keyPath = KEY_PATH_SETTING.get(settings).orElse(null); + String keyStorePath = SETTINGS_PARSER.keystorePath.get(settings).orElse(null); + String keyPath = SETTINGS_PARSER.keyPath.get(settings).orElse(null); if (keyPath != null && keyStorePath != null) { throw new IllegalArgumentException("you cannot specify a keystore and key file"); } else if (keyStorePath == null && keyPath == null) { @@ -233,29 +190,29 @@ class SSLConfiguration { } if (keyPath != null) { - String keyPassword = KEY_PASSWORD_SETTING.get(settings).orElse(null); - String certPath = CERT_SETTING.get(settings).orElse(null); + String keyPassword = SETTINGS_PARSER.keyPassword.get(settings).orElse(null); + String certPath = SETTINGS_PARSER.cert.get(settings).orElse(null); if (certPath == null) { throw new IllegalArgumentException("you must specify the certificates to use with the key"); } return new PEMKeyConfig(keyPath, keyPassword, certPath); } else { - String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null); - String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings); - String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword); - String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings); + String keyStorePassword = SETTINGS_PARSER.keystorePassword.get(settings).orElse(null); + String keyStoreAlgorithm = SETTINGS_PARSER.keystoreAlgorithm.get(settings); + String keyStoreKeyPassword = SETTINGS_PARSER.keystoreKeyPassword.get(settings).orElse(keyStorePassword); + String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings); return new StoreKeyConfig(keyStorePath, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm, trustStoreAlgorithm); } } private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) { - String trustStorePath = TRUSTSTORE_PATH_SETTING.get(settings).orElse(null); - List caPaths = getListOrNull(CA_PATHS_SETTING, settings); + String trustStorePath = SETTINGS_PARSER.truststorePath.get(settings).orElse(null); + List caPaths = getListOrNull(SETTINGS_PARSER.caPaths, settings); if (trustStorePath != null && caPaths != null) { throw new IllegalArgumentException("you cannot specify a truststore and ca files"); } - VerificationMode verificationMode = VERIFICATION_MODE_SETTING.get(settings).orElseGet(() -> { + VerificationMode verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElseGet(() -> { if (global != null) { return global.verificationMode(); } @@ -266,8 +223,8 @@ class SSLConfiguration { } else if (caPaths != null) { return new PEMTrustConfig(caPaths); } else if (trustStorePath != null) { - String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null); - String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings); + String trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings).orElse(null); + String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings); return new StoreTrustConfig(trustStorePath, trustStorePassword, trustStoreAlgorithm); } else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null) { return new StoreTrustConfig(System.getProperty("javax.net.ssl.trustStore"), diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettings.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettings.java new file mode 100644 index 00000000000..ac099931009 --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettings.java @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +/** + * Bridges {@link SSLConfiguration} into the {@link Settings} framework, using {@link Setting} objects. + */ +public class SSLConfigurationSettings { + + private final String prefix; + + public final Setting> ciphers; + public final Setting> supportedProtocols; + public final Setting> keystorePath; + public final Setting> keystorePassword; + public final Setting keystoreAlgorithm; + public final Setting> keystoreKeyPassword; + public final Setting> truststorePath; + public final Setting> truststorePassword; + public final Setting truststoreAlgorithm; + public final Setting> keyPath; + public final Setting> keyPassword; + public final Setting> cert; + public final Setting> caPaths; + public final Setting> clientAuth; + public final Setting> verificationMode; + + /** + * @see #withoutPrefix + * @see #withPrefix + * @param prefix The prefix under which each setting should be defined. Must be either the empty string ("") or a string + * ending in "." + */ + private SSLConfigurationSettings(String prefix) { + assert prefix != null : "Prefix cannot be null (but can be blank)"; + this.prefix = prefix; + + ciphers = list("cipher_suites", Collections.emptyList()); + supportedProtocols = list("supported_protocols", Collections.emptyList()); + keystorePath = optionalString("keystore.path"); + keystorePassword = optionalString("keystore.password"); + keystoreKeyPassword = optionalString("keystore.key_password", keystorePassword); + truststorePath = optionalString("truststore.path"); + truststorePassword = optionalString("truststore.password"); + keystoreAlgorithm = systemProperty("keystore.algorithm", + "ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + truststoreAlgorithm = systemProperty("truststore.algorithm", "ssl.TrustManagerFactory.algorithm", + TrustManagerFactory.getDefaultAlgorithm()); + keyPath = optionalString("key"); + keyPassword = optionalString("key_passphrase"); + cert = optionalString("certificate"); + caPaths = list("certificate_authorities", Collections.emptyList()); + clientAuth = optional("client_authentication", SSLClientAuth::parse); + verificationMode = optional("verification_mode", VerificationMode::parse); + } + + public List> getAllSettings() { + return Arrays.asList(ciphers, supportedProtocols, + keystorePath, keystorePassword, keystoreAlgorithm, keystoreKeyPassword, + truststorePath, truststorePassword, truststoreAlgorithm, + keyPath, keyPassword, + cert, caPaths, clientAuth, verificationMode); + } + + private Setting> optionalString(String keyPart) { + return optionalString(keyPart, (s) -> null); + } + + private Setting> optionalString(String keyPart, Function defaultValue) { + return new Setting<>(prefix + keyPart, defaultValue, Optional::ofNullable, + Setting.Property.NodeScope, Setting.Property.Filtered); + } + + private Setting> optionalString(String keyPart, Setting> fallback) { + return new Setting<>(prefix + keyPart, fallback, Optional::ofNullable, + Setting.Property.NodeScope, Setting.Property.Filtered); + } + + private Setting> optional(String keyPart, Function parserIfNotNull) { + Function> parser = s -> { + if (s == null) { + return Optional.empty(); + } else { + return Optional.of(parserIfNotNull.apply(s)); + } + }; + return new Setting<>(prefix + keyPart, (String) null, parser, Setting.Property.NodeScope, Setting.Property.Filtered); + } + + private Setting systemProperty(String keyPart, String systemProperty, String defaultValue) { + return string(keyPart, s -> System.getProperty(systemProperty, defaultValue)); + } + + private Setting string(String keyPart, Function defaultFunction) { + return new Setting<>(prefix + keyPart, defaultFunction, Function.identity(), + Setting.Property.NodeScope, Setting.Property.Filtered); + } + + private Setting> list(String keyPart, List defaultValue) { + return Setting.listSetting(prefix + keyPart, defaultValue, Function.identity(), + Setting.Property.NodeScope, Setting.Property.Filtered); + } + + /** + * Construct settings that are un-prefixed. That is, they can be used to read from a {@link Settings} object where the configuration + * keys are the root names of the Settings. + */ + public static SSLConfigurationSettings withoutPrefix() { + return new SSLConfigurationSettings(""); + } + + /** + * Construct settings that have a prefixed. That is, they can be used to read from a {@link Settings} object where the configuration + * keys are prefixed-children of the Settings. + * @param prefix A string that must end in "ssl." + */ + public static SSLConfigurationSettings withPrefix(String prefix) { + assert prefix.endsWith("ssl.") : "The ssl config prefix (" + prefix + ") should end in 'ssl.'"; + return new SSLConfigurationSettings(prefix); + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java index fa1a1c7f963..76f60e04f4e 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/ssl/SSLService.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.xpack.XPackSettings; +import org.elasticsearch.xpack.security.Security; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -62,7 +63,7 @@ public class SSLService extends AbstractComponent { public SSLService(Settings settings, Environment environment) { super(settings); this.env = environment; - this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix("xpack.ssl.")); + this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX)); this.sslContexts = loadSSLConfigurations(); } @@ -387,7 +388,7 @@ public class SSLService extends AbstractComponent { Map sslConfigurations = new HashMap<>(); sslConfigurations.put(globalSSLConfiguration, createSslContext(globalSSLConfiguration)); - final Settings transportSSLSettings = settings.getByPrefix("xpack.security.transport.ssl."); + final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX); List sslSettingsList = new ArrayList<>(); sslSettingsList.add(transportSSLSettings); sslSettingsList.add(getHttpTransportSSLSettings(settings)); @@ -741,7 +742,7 @@ public class SSLService extends AbstractComponent { private static List getRealmsSSLSettings(Settings settings) { List sslSettings = new ArrayList<>(); - Settings realmsSettings = settings.getByPrefix("xpack.security.authc.realms."); + Settings realmsSettings = settings.getByPrefix(Security.setting("authc.realms.")); for (String name : realmsSettings.names()) { Settings realmSSLSettings = realmsSettings.getAsSettings(name).getByPrefix("ssl."); if (realmSSLSettings.isEmpty() == false) { @@ -764,7 +765,7 @@ public class SSLService extends AbstractComponent { } public static Settings getHttpTransportSSLSettings(Settings settings) { - Settings httpSSLSettings = settings.getByPrefix("xpack.security.http.ssl."); + Settings httpSSLSettings = settings.getByPrefix(XPackSettings.HTTP_SSL_PREFIX); if (httpSSLSettings.isEmpty()) { return httpSSLSettings; } diff --git a/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java b/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java index e5323d315b5..54d0d439bc6 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/test/SettingsFilterTests.java @@ -39,7 +39,7 @@ public class SettingsFilterTests extends ESTestCase { configureUnfilteredSetting("xpack.security.authc.realms.ldap1.type", "ldap"); configureUnfilteredSetting("xpack.security.authc.realms.ldap1.enabled", "false"); configureUnfilteredSetting("xpack.security.authc.realms.ldap1.url", "ldap://host.domain"); - configureFilteredSetting("xpack.security.authc.realms.ldap1.hostname_verification", randomAsciiOfLength(5)); + configureFilteredSetting("xpack.security.authc.realms.ldap1.hostname_verification", randomBooleanSetting()); configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_dn", randomAsciiOfLength(5)); configureFilteredSetting("xpack.security.authc.realms.ldap1.bind_password", randomAsciiOfLength(5)); @@ -47,7 +47,7 @@ public class SettingsFilterTests extends ESTestCase { configureUnfilteredSetting("xpack.security.authc.realms.ad1.type", "active_directory"); configureUnfilteredSetting("xpack.security.authc.realms.ad1.enabled", "false"); configureUnfilteredSetting("xpack.security.authc.realms.ad1.url", "ldap://host.domain"); - configureFilteredSetting("xpack.security.authc.realms.ad1.hostname_verification", randomAsciiOfLength(5)); + configureFilteredSetting("xpack.security.authc.realms.ad1.hostname_verification", randomBooleanSetting()); // pki filtering configureUnfilteredSetting("xpack.security.authc.realms.pki1.type", "pki"); @@ -115,6 +115,10 @@ public class SettingsFilterTests extends ESTestCase { } } + private String randomBooleanSetting() { + return randomFrom("true", "1", "on", "yes", "false", "0", "off", "no"); + } + private void configureUnfilteredSetting(String settingName, String value) { configureSetting(settingName, value, is(value)); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java index e258f09bcd5..5bc266a61ac 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/common/http/HttpClientTests.java @@ -284,8 +284,8 @@ public class HttpClientTests extends ESTestCase { proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody("fullProxiedContent")); proxyServer.start(); Settings settings = Settings.builder() - .put(HttpClient.SETTINGS_PROXY_HOST, "localhost") - .put(HttpClient.SETTINGS_PROXY_PORT, proxyServer.getPort()) + .put(HttpSettings.PROXY_HOST.getKey(), "localhost") + .put(HttpSettings.PROXY_PORT.getKey(), proxyServer.getPort()) .build(); HttpClient httpClient = new HttpClient(settings, authRegistry, new SSLService(settings, environment)); @@ -309,8 +309,8 @@ public class HttpClientTests extends ESTestCase { proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody("fullProxiedContent")); proxyServer.start(); Settings settings = Settings.builder() - .put(HttpClient.SETTINGS_PROXY_HOST, "localhost") - .put(HttpClient.SETTINGS_PROXY_PORT, proxyServer.getPort() + 1) + .put(HttpSettings.PROXY_HOST.getKey(), "localhost") + .put(HttpSettings.PROXY_PORT.getKey(), proxyServer.getPort() + 1) .build(); HttpClient httpClient = new HttpClient(settings, authRegistry, new SSLService(settings, environment)); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 5199d0d7492..c00af8f0151 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -65,7 +65,7 @@ public class SecurityTests extends ESTestCase { ThreadPool threadPool = mock(ThreadPool.class); ClusterService clusterService = mock(ClusterService.class); settings = Security.additionalSettings(settings, false); - Set> allowedSettings = new HashSet<>(Security.getSettings(false)); + Set> allowedSettings = new HashSet<>(Security.getSettings(false, null)); allowedSettings.addAll(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); ClusterSettings clusterSettings = new ClusterSettings(settings, allowedSettings); when(clusterService.getClusterSettings()).thenReturn(clusterSettings); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java new file mode 100644 index 00000000000..91281769bb0 --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java @@ -0,0 +1,288 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.XPackSettings; +import org.elasticsearch.xpack.extensions.XPackExtension; +import org.elasticsearch.xpack.security.authc.support.Hasher; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; + +public class RealmSettingsTests extends ESTestCase { + + private static final List HASH_ALGOS = Arrays.stream(Hasher.values()).map(Hasher::name).collect(Collectors.toList()); + + public void testRealmWithoutTypeDoesNotValidate() throws Exception { + final Settings.Builder builder = baseSettings("x", false); + builder.remove("type"); + assertErrorWithMessage("empty1", "missing realm type", realm("empty1", builder).build()); + } + + public void testRealmWithBlankTypeDoesNotValidate() throws Exception { + final Settings.Builder builder = baseSettings("", false); + assertErrorWithMessage("empty2", "missing realm type", realm("empty2", builder).build()); + } + + /** + * This test exists because (in 5.x), we want to be backwards compatible and accept custom realms that + * have not been updated to explicitly declare their settings. + * + * @see XPackExtension#getRealmSettings() + */ + public void testRealmWithUnknownTypeAcceptsAllSettings() throws Exception { + final Settings.Builder settings = baseSettings("tam", true) + .put("ip", "8.6.75.309") + .put(randomAsciiOfLengthBetween(4, 8), randomTimeValue()); + assertSuccess(realm("tam", settings)); + } + + public void testFileRealmWithAllSettingsValidatesSuccessfully() throws Exception { + assertSuccess(fileRealm("file1")); + } + + public void testFileRealmWithUnknownConfigurationDoesNotValidate() throws Exception { + final Settings.Builder builder = realm("file2", fileSettings().put("not-valid", randomInt())); + assertErrorWithCause("file2", "unknown setting [not-valid]", builder.build()); + } + + public void testNativeRealmWithAllSettingsValidatesSuccessfully() throws Exception { + assertSuccess(nativeRealm("native1")); + } + + public void testNativeRealmWithUnknownConfigurationDoesNotValidate() throws Exception { + final Settings.Builder builder = realm("native2", nativeSettings().put("not-valid", randomAsciiOfLength(10))); + assertErrorWithCause("native2", "unknown setting [not-valid]", builder.build()); + } + + public void testLdapRealmWithUserTemplatesAndGroupAttributesValidatesSuccessfully() throws Exception { + assertSuccess(ldapRealm("ldap1", false, false)); + } + + public void testLdapRealmWithUserSearchAndGroupSearchValidatesSuccessfully() throws Exception { + assertSuccess(ldapRealm("ldap2", true, true)); + } + + public void testActiveDirectoryRealmWithAllSettingsValidatesSuccessfully() throws Exception { + assertSuccess(activeDirectoryRealm("ad1")); + } + + public void testPkiRealmWithCertificateAuthoritiesValidatesSuccessfully() throws Exception { + assertSuccess(pkiRealm("pki1", false)); + } + + public void testPkiRealmWithTrustStoreValidatesSuccessfully() throws Exception { + assertSuccess(pkiRealm("pki2", true)); + } + + public void testPkiRealmWithFullSslSettingsDoesNotValidate() throws Exception { + final Settings.Builder realm = realm("pki3", configureSsl("", pkiSettings(true), true, true)); + assertError("pki3", realm.build()); + } + + public void testSettingsWithMultipleRealmsValidatesSuccessfully() throws Exception { + final Settings settings = Settings.builder() + .put(fileRealm("file1").build()) + .put(nativeRealm("native2").build()) + .put(ldapRealm("ldap3", true, false).build()) + .put(activeDirectoryRealm("ad4").build()) + .put(pkiRealm("pki5", false).build()) + .build(); + assertSuccess(settings); + } + + private Settings.Builder nativeRealm(String name) { + return realm(name, nativeSettings()); + } + + private Settings.Builder nativeSettings() { + return baseSettings("native", true); + } + + private Settings.Builder fileRealm(String name) { + return realm(name, fileSettings()); + } + + private Settings.Builder fileSettings() { + return baseSettings("file", true); + } + + private Settings.Builder ldapRealm(String name, boolean userSearch, boolean groupSearch) { + return realm(name, ldapSettings(userSearch, groupSearch)); + } + + private Settings.Builder ldapSettings(boolean userSearch, boolean groupSearch) { + final Settings.Builder builder = commonLdapSettings("ldap") + .put("bind_dn", "elasticsearch") + .put("bind_password", "t0p_s3cr3t") + .put("follow_referrals", randomBoolean()); + + if (userSearch) { + builder.put("user_search.base_dn", "o=people, dc=example, dc=com"); + builder.put("user_search.scope", "sub_tree"); + builder.put("user_search.attribute", randomAsciiOfLengthBetween(2, 5)); + builder.put("user_search.pool.enabled", randomBoolean()); + builder.put("user_search.pool.size", randomIntBetween(10, 100)); + builder.put("user_search.pool.initial_size", randomIntBetween(1, 10)); + builder.put("user_search.pool.health_check.enabled", randomBoolean()); + builder.put("user_search.pool.health_check.dn", randomAsciiOfLength(32)); + builder.put("user_search.pool.health_check.interval", randomPositiveTimeValue()); + } else { + builder.putArray("user_dn_templates", + "cn={0}, ou=staff, o=people, dc=example, dc=com", + "cn={0}, ou=visitors, o=people, dc=example, dc=com"); + } + + if (groupSearch) { + builder.put("group_search.base_dn", "o=groups, dc=example, dc=com"); + builder.put("group_search.scope", "one_level"); + builder.put("group_search.filter", "userGroup"); + builder.put("group_search.user_attribute", "uid"); + } else { + builder.put("user_group_attribute", randomAsciiOfLength(8)); + } + return builder; + } + + private Settings.Builder activeDirectoryRealm(String name) { + return realm(name, activeDirectorySettings()); + } + + private Settings.Builder activeDirectorySettings() { + final Settings.Builder builder = commonLdapSettings("active_directory") + .put("domain_name", "MEGACORP"); + builder.put("user_search.base_dn", "o=people, dc.example, dc.com"); + builder.put("user_search.scope", "sub_tree"); + builder.put("user_search.filter", randomAsciiOfLength(5) + "={0}"); + builder.put("group_search.base_dn", "o=groups, dc=example, dc=com"); + builder.put("group_search.scope", "one_level"); + return builder; + } + + private Settings.Builder commonLdapSettings(String type) { + final Settings.Builder builder = baseSettings(type, true) + .putArray("url", "ldap://dir1.internal:9876", "ldap://dir2.internal:9876", "ldap://dir3.internal:9876") + .put("load_balance.type", "round_robin") + .put("load_balance.cache_ttl", randomTimeValue()) + .put("unmapped_groups_as_roles", randomBoolean()) + .put("files.role_mapping", "x-pack/" + randomAsciiOfLength(8) + ".yml") + .put("timeout.tcp_connect", randomPositiveTimeValue()) + .put("timeout.tcp_read", randomPositiveTimeValue()) + .put("timeout.ldap_search", randomPositiveTimeValue()); + configureSsl("ssl.", builder, randomBoolean(), randomBoolean()); + return builder; + } + + private Settings.Builder pkiRealm(String name, boolean useTrustStore) { + return realm(name, pkiSettings(useTrustStore)); + } + + private Settings.Builder pkiSettings(boolean useTrustStore) { + final Settings.Builder builder = baseSettings("pki", false) + .put("username_pattern", "CN=\\D(\\d+)(?:,\\|$)") + .put("files.role_mapping", "x-pack/" + randomAsciiOfLength(8) + ".yml"); + + if (useTrustStore) { + builder.put("truststore.path", randomAsciiOfLengthBetween(8, 32)); + builder.put("truststore.password", randomAsciiOfLengthBetween(4, 12)); + builder.put("truststore.algorithm", randomAsciiOfLengthBetween(6, 10)); + } else { + builder.putArray("certificate_authorities", generateRandomStringArray(5, 32, false, false)); + } + return builder; + } + + private Settings.Builder configureSsl(String prefix, Settings.Builder builder, boolean useKeyStore, boolean useTrustStore) { + if (useKeyStore) { + builder.put(prefix + "keystore.path", "x-pack/ssl/" + randomAsciiOfLength(5) + ".jks"); + builder.put(prefix + "keystore.password", randomAsciiOfLength(8)); + builder.put(prefix + "keystore.key_password", randomAsciiOfLength(8)); + } else { + builder.put(prefix + "key", "x-pack/ssl/" + randomAsciiOfLength(5) + ".key"); + builder.put(prefix + "key_passphrase", randomAsciiOfLength(32)); + builder.put(prefix + "certificate", "x-pack/ssl/" + randomAsciiOfLength(5) + ".cert"); + } + + if (useTrustStore) { + builder.put(prefix + "truststore.path", "x-pack/ssl/" + randomAsciiOfLength(5) + ".jts"); + builder.put(prefix + "truststore.password", randomAsciiOfLength(8)); + } else { + builder.put(prefix + "certificate_authorities", "x-pack/ssl/" + randomAsciiOfLength(8) + ".ca"); + } + + builder.put(prefix + "verification_mode", "full"); + builder.putArray(prefix + "supported_protocols", randomSubsetOf(XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS)); + builder.putArray(prefix + "cipher_suites", randomSubsetOf(XPackSettings.DEFAULT_CIPHERS)); + + return builder; + } + + private Settings.Builder baseSettings(String type, boolean withCacheSettings) { + final Settings.Builder builder = Settings.builder() + .put("type", type) + .put("order", randomInt()) + .put("enabled", true); + if (withCacheSettings) { + builder.put("cache.ttl", randomPositiveTimeValue()) + .put("cache.max_users", randomIntBetween(1_000, 1_000_000)) + .put("cache.hash_algo", randomFrom(HASH_ALGOS)); + } + return builder; + } + + private Settings.Builder realm(String name, Settings.Builder settings) { + return settings.normalizePrefix(realmPrefix(name)); + } + + private String realmPrefix(String name) { + return RealmSettings.PREFIX + name + "."; + } + + private void assertSuccess(Settings.Builder builder) { + assertSuccess(builder.build()); + } + + private void assertSuccess(Settings settings) { + assertThat(group().get(settings), notNullValue()); + } + + private void assertErrorWithCause(String realmName, String message, Settings settings) { + final IllegalArgumentException exception = assertError(realmName, settings); + assertThat(exception.getCause(), notNullValue()); + assertThat(exception.getCause().getMessage(), containsString(message)); + } + + private void assertErrorWithMessage(String realmName, String message, Settings settings) { + final IllegalArgumentException exception = assertError(realmName, settings); + assertThat(exception.getMessage(), containsString(message)); + } + + private IllegalArgumentException assertError(String realmName, Settings settings) { + final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, + () -> group().get(settings) + ); + assertThat(exception.getMessage(), containsString(realmPrefix(realmName))); + return exception; + } + + private Setting group() { + final List> list = new ArrayList<>(); + final List noExtensions = Collections.emptyList(); + RealmSettings.addSettings(list, noExtensions); + assertThat(list, hasSize(1)); + return list.get(0); + } +} \ No newline at end of file diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java index ef3646d5697..4414269700b 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java @@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify; public class ActiveDirectoryRealmTests extends ESTestCase { private static final String PASSWORD = "password"; + private static final String ROLE_MAPPING_FILE_SETTING = DnRoleMapper.ROLE_MAPPING_FILE_SETTING.getKey(); static int numberOfLdapServers; InMemoryDirectoryServer[] directoryServers; @@ -168,7 +169,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { } public void testAuthenticateCachingCanBeDisabled() throws Exception { - Settings settings = settings(Settings.builder().put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING, -1).build()); + Settings settings = settings(Settings.builder().put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING.getKey(), -1).build()); RealmConfig config = new RealmConfig("testAuthenticateCachingCanBeDisabled", settings, globalSettings); ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService, () -> {}); @@ -216,7 +217,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testRealmMapsGroupsToRoles() throws Exception { Settings settings = settings(Settings.builder() - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) + .put(ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); @@ -232,7 +233,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testRealmMapsUsersToRoles() throws Exception { Settings settings = settings(Settings.builder() - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) + .put(ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); @@ -249,7 +250,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testRealmUsageStats() throws Exception { String loadBalanceType = randomFrom("failover", "round_robin"); Settings settings = settings(Settings.builder() - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) + .put(ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .put("load_balance.type", loadBalanceType) .build()); RealmConfig config = new RealmConfig("testRealmUsageStats", settings, globalSettings); @@ -274,7 +275,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { return Settings.builder() .putArray(URLS_SETTING, ldapUrls()) .put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, "ad.test.elasticsearch.com") - .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true) + .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING.getKey(), true) .put(HOSTNAME_VERIFICATION_SETTING, false) .put(extraSettings) .build(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 3dfc0d15ca6..dc0f4d3cdd9 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; +import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.SecuredStringTests; @@ -23,9 +24,9 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.junit.After; import org.junit.Before; +import java.util.Arrays; import java.util.Map; -import static org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory.USER_DN_TEMPLATES_SETTING; import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.HOSTNAME_VERIFICATION_SETTING; import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.URLS_SETTING; import static org.hamcrest.Matchers.arrayContaining; @@ -46,6 +47,8 @@ public class LdapRealmTests extends LdapTestCase { public static final String VALID_USERNAME = "Thomas Masterman Hardy"; public static final String PASSWORD = "pass"; + private static final String USER_DN_TEMPLATES_SETTING_KEY = LdapSessionFactory.USER_DN_TEMPLATES_SETTING.getKey(); + private ThreadPool threadPool; private ResourceWatcherService resourceWatcherService; private Settings globalSettings; @@ -95,7 +98,7 @@ public class LdapRealmTests extends LdapTestCase { ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future); User user = future.actionGet(); assertThat(user, notNullValue()); - assertThat(user.roles(), arrayContaining("HMS Victory")); + assertThat("For roles " + Arrays.toString(user.roles()), user.roles(), arrayContaining("HMS Victory")); } public void testAuthenticateCaching() throws Exception { @@ -158,7 +161,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = Settings.builder() .put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) - .put(LdapRealm.CACHE_TTL_SETTING, -1) + .put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING.getKey(), -1) .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); @@ -182,7 +185,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = Settings.builder() .putArray(URLS_SETTING, ldapUrls()) - .putArray(USER_DN_TEMPLATES_SETTING, userTemplate) + .putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate) .put("group_search.base_dn", groupSearchBase) .put("group_search.scope", LdapSearchScope.SUB_TREE) .put(HOSTNAME_VERIFICATION_SETTING, false) @@ -215,7 +218,7 @@ public class LdapRealmTests extends LdapTestCase { public void testLdapRealmThrowsExceptionForUserTemplateAndSearchSettings() throws Exception { Settings settings = Settings.builder() .putArray(URLS_SETTING, ldapUrls()) - .putArray(USER_DN_TEMPLATES_SETTING, "cn=foo") + .putArray(USER_DN_TEMPLATES_SETTING_KEY, "cn=foo") .put("user_search.base_dn", "cn=bar") .put("group_search.base_dn", "") .put("group_search.scope", LdapSearchScope.SUB_TREE) @@ -224,7 +227,26 @@ public class LdapRealmTests extends LdapTestCase { RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings, globalSettings); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> LdapRealm.sessionFactory(config, null, LdapRealm.LDAP_TYPE)); - assertThat(e.getMessage(), containsString("settings were found for both user search and user template")); + assertThat(e.getMessage(), + containsString("settings were found for both" + + " user search [xpack.security.authc.realms.test-ldap-realm-user-search.user_search.] and" + + " user template [xpack.security.authc.realms.test-ldap-realm-user-search.user_dn_templates]")); + } + + public void testLdapRealmThrowsExceptionWhenNeitherUserTemplateNorSearchSettingsProvided() throws Exception { + Settings settings = Settings.builder() + .putArray(URLS_SETTING, ldapUrls()) + .put("group_search.base_dn", "") + .put("group_search.scope", LdapSearchScope.SUB_TREE) + .put(HOSTNAME_VERIFICATION_SETTING, false) + .build(); + RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings, globalSettings); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> LdapRealm.sessionFactory(config, null, LdapRealm.LDAP_TYPE)); + assertThat(e.getMessage(), + containsString("settings were not found for either" + + " user search [xpack.security.authc.realms.test-ldap-realm-user-search.user_search.] or" + + " user template [xpack.security.authc.realms.test-ldap-realm-user-search.user_dn_templates]")); } public void testLdapRealmMapsUserDNToRole() throws Exception { @@ -232,7 +254,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = Settings.builder() .put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, + .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING.getKey(), getDataPath("/org/elasticsearch/xpack/security/authc/support/role_mapping.yml")) .build(); RealmConfig config = new RealmConfig("test-ldap-realm-userdn", settings, globalSettings); @@ -256,6 +278,7 @@ public class LdapRealmTests extends LdapTestCase { .put("bind_password", PASSWORD) .put("group_search.base_dn", groupSearchBase) .put("group_search.scope", LdapSearchScope.SUB_TREE) + .put(LdapSessionFactory.USER_DN_TEMPLATES_SETTING.getKey(), "--") .put(HOSTNAME_VERIFICATION_SETTING, false); int order = randomIntBetween(0, 10); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java index 78473a58f97..c0db55d5a11 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java @@ -27,7 +27,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testResolveSubTree() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -42,8 +42,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testResolveOneLevel() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("scope", LdapSearchScope.ONE_LEVEL) + .put("group_search.base_dn", "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.scope", LdapSearchScope.ONE_LEVEL) .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -58,8 +58,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testResolveBase() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "cn=Avengers,ou=People,dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("scope", LdapSearchScope.BASE) + .put("group_search.base_dn", "cn=Avengers,ou=People,dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.scope", LdapSearchScope.BASE) .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -70,9 +70,9 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testResolveCustomFilter() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("filter", "(&(objectclass=posixGroup)(memberUID={0}))") - .put("user_attribute", "uid") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.filter", "(&(objectclass=posixGroup)(memberUID={0}))") + .put("group_search.user_attribute", "uid") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -84,8 +84,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testFilterIncludesPosixGroups() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("user_attribute", "uid") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.user_attribute", "uid") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -97,7 +97,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testCreateWithoutSpecifyingBaseDN() throws Exception { Settings settings = Settings.builder() - .put("scope", LdapSearchScope.SUB_TREE) + .put("group_search.scope", LdapSearchScope.SUB_TREE) .build(); try { @@ -110,8 +110,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testReadUserAttributeUid() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("user_attribute", "uid").build(); + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.user_attribute", "uid").build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); PlainActionFuture future = new PlainActionFuture<>(); resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), future); @@ -120,8 +120,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testReadUserAttributeCn() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("user_attribute", "cn") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.user_attribute", "cn") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -132,8 +132,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testReadNonExistentUserAttribute() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("user_attribute", "doesntExists") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.user_attribute", "doesntExists") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); @@ -144,8 +144,8 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { public void testReadBinaryUserAttribute() throws Exception { Settings settings = Settings.builder() - .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") - .put("user_attribute", "userPassword") + .put("group_search.base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("group_search.user_attribute", "userPassword") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java index d574cc01ab1..30c5bfaf40d 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; +import org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.watcher.ResourceWatcherService; @@ -28,10 +29,11 @@ import java.util.Objects; import static org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory.HOSTNAME_VERIFICATION_SETTING; import static org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory.URLS_SETTING; -import static org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory.USER_DN_TEMPLATES_SETTING; public abstract class LdapTestCase extends ESTestCase { + private static final String USER_DN_TEMPLATES_SETTING_KEY = LdapSessionFactory.USER_DN_TEMPLATES_SETTING.getKey(); + static int numberOfLdapServers; protected InMemoryDirectoryServer[] ldapServers; @@ -86,7 +88,7 @@ public abstract class LdapTestCase extends ESTestCase { LdapLoadBalancing serverSetType) { Settings.Builder builder = Settings.builder() .putArray(URLS_SETTING, ldapUrl) - .putArray(USER_DN_TEMPLATES_SETTING, userTemplate) + .putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate) .put("group_search.base_dn", groupSearchBase) .put("group_search.scope", scope) .put(HOSTNAME_VERIFICATION_SETTING, false); @@ -100,14 +102,14 @@ public abstract class LdapTestCase extends ESTestCase { public static Settings buildLdapSettings(String[] ldapUrl, String userTemplate, boolean hostnameVerification) { return Settings.builder() .putArray(URLS_SETTING, ldapUrl) - .putArray(USER_DN_TEMPLATES_SETTING, userTemplate) + .putArray(USER_DN_TEMPLATES_SETTING_KEY, userTemplate) .put(HOSTNAME_VERIFICATION_SETTING, hostnameVerification) .build(); } protected DnRoleMapper buildGroupAsRoleMapper(ResourceWatcherService resourceWatcherService) { Settings settings = Settings.builder() - .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true) + .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING.getKey(), true) .build(); Settings global = Settings.builder().put("path.home", createTempDir()).build(); RealmConfig config = new RealmConfig("ldap1", settings, global); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java index 771f5874765..2c28479bdcd 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java @@ -42,9 +42,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase { int maxUsers = randomIntBetween(10, 100); TimeValue ttl = TimeValue.timeValueMinutes(randomIntBetween(10, 20)); Settings settings = Settings.builder() - .put(CachingUsernamePasswordRealm.CACHE_HASH_ALGO_SETTING, hashAlgo) - .put(CachingUsernamePasswordRealm.CACHE_MAX_USERS_SETTING, maxUsers) - .put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING, ttl) + .put(CachingUsernamePasswordRealm.CACHE_HASH_ALGO_SETTING.getKey(), hashAlgo) + .put(CachingUsernamePasswordRealm.CACHE_MAX_USERS_SETTING.getKey(), maxUsers) + .put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING.getKey(), ttl) .build(); RealmConfig config = new RealmConfig("test_realm", settings, globalSettings); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java index 9347de01031..85b23170256 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java @@ -47,6 +47,9 @@ import static org.hamcrest.Matchers.notNullValue; public class DnRoleMapperTests extends ESTestCase { + private static final String ROLE_MAPPING_FILE_SETTING = DnRoleMapper.ROLE_MAPPING_FILE_SETTING.getKey(); + private static final String USE_UNMAPPED_GROUPS_AS_ROLES_SETTING_KEY = DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING.getKey(); + private static final String[] STARK_GROUP_DNS = new String[] { //groups can be named by different attributes, depending on the directory, //we don't care what it is named by @@ -230,7 +233,7 @@ public class DnRoleMapperTests extends ESTestCase { public void testYaml() throws Exception { Path file = getDataPath("role_mapping.yml"); Settings ldapSettings = Settings.builder() - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, file.toAbsolutePath()) + .put(ROLE_MAPPING_FILE_SETTING, file.toAbsolutePath()) .build(); RealmConfig config = new RealmConfig("ldap1", ldapSettings, settings); @@ -244,7 +247,7 @@ public class DnRoleMapperTests extends ESTestCase { public void testRelativeDN() { Settings ldapSettings = Settings.builder() - .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true) + .put(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING_KEY, true) .build(); RealmConfig config = new RealmConfig("ldap1", ldapSettings, settings); @@ -257,8 +260,8 @@ public class DnRoleMapperTests extends ESTestCase { public void testUserDNMapping() throws Exception { Path file = getDataPath("role_mapping.yml"); Settings ldapSettings = Settings.builder() - .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, file.toAbsolutePath()) - .put(DnRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, false) + .put(ROLE_MAPPING_FILE_SETTING, file.toAbsolutePath()) + .put(USE_UNMAPPED_GROUPS_AS_ROLES_SETTING_KEY, false) .build(); RealmConfig config = new RealmConfig("ldap-userdn-role", ldapSettings, settings); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettingsTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettingsTests.java new file mode 100644 index 00000000000..ffbc33541dc --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLConfigurationSettingsTests.java @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ssl; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.util.Arrays; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.is; + +public class SSLConfigurationSettingsTests extends ESTestCase { + + public void testParseCipherSettingsWithoutPrefix() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withoutPrefix(); + assertThat(ssl.ciphers.match("cipher_suites"), is(true)); + assertThat(ssl.ciphers.match("ssl.cipher_suites"), is(false)); + assertThat(ssl.ciphers.match("xpack.ssl.cipher_suites"), is(false)); + + final Settings settings = Settings.builder() + .put("cipher_suites.0", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") + .put("cipher_suites.1", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256") + .put("cipher_suites.2", "TLS_RSA_WITH_AES_128_CBC_SHA256") + .build(); + assertThat(ssl.ciphers.get(settings), is(Arrays.asList( + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256" + ))); + } + + public void testParseClientAuthWithPrefix() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("xpack.security.http.ssl."); + assertThat(ssl.clientAuth.match("xpack.security.http.ssl.client_authentication"), is(true)); + assertThat(ssl.clientAuth.match("client_authentication"), is(false)); + + final Settings settings = Settings.builder() + .put("xpack.security.http.ssl.client_authentication", SSLClientAuth.OPTIONAL.name()) + .build(); + assertThat(ssl.clientAuth.get(settings).get(), is(SSLClientAuth.OPTIONAL)); + } + + public void testParseKeystoreAlgorithmWithPrefix() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("xpack.security.authc.realms.ldap1.ssl."); + assertThat(ssl.keystoreAlgorithm.match("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm"), is(true)); + + final String algo = randomAsciiOfLength(16); + final Settings settings = Settings.builder() + .put("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm", algo) + .build(); + assertThat(ssl.keystoreAlgorithm.get(settings), is(algo)); + } + + public void testParseProtocolsListWithPrefix() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("ssl."); + assertThat(ssl.supportedProtocols.match("ssl.supported_protocols"), is(true)); + + final Settings settings = Settings.builder() + .putArray("ssl.supported_protocols", "SSLv3", "SSLv2Hello", "SSLv2") + .build(); + assertThat(ssl.supportedProtocols.get(settings), is(Arrays.asList("SSLv3", "SSLv2Hello", "SSLv2"))); + } + + public void testKeyStoreKeyPasswordDefaultsToKeystorePassword() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("xpack.ssl."); + + assertThat(ssl.keystorePassword.match("xpack.ssl.keystore.password"), is(true)); + assertThat(ssl.keystoreKeyPassword.match("xpack.ssl.keystore.key_password"), is(true)); + + assertThat(ssl.keystorePassword.match("xpack.ssl.keystore.key_password"), is(false)); + assertThat(ssl.keystoreKeyPassword.match("xpack.ssl.keystore.password"), is(false)); + + final String password = randomAsciiOfLength(16); + final Settings settings = Settings.builder() + .put("xpack.ssl.keystore.password", password) + .build(); + assertThat(ssl.keystoreKeyPassword.get(settings).get(), is(password)); + } + + public void testEmptySettingsParsesToDefaults() { + final SSLConfigurationSettings ssl = SSLConfigurationSettings.withoutPrefix(); + final Settings settings = Settings.EMPTY; + assertThat(ssl.caPaths.get(settings).size(), is(0)); + assertThat(ssl.cert.get(settings).isPresent(), is(false)); + assertThat(ssl.ciphers.get(settings).size(), is(0)); + assertThat(ssl.clientAuth.get(settings).isPresent(), is(false)); + assertThat(ssl.keyPassword.get(settings).isPresent(), is(false)); + assertThat(ssl.keyPath.get(settings).isPresent(), is(false)); + assertThat(ssl.keystoreAlgorithm.get(settings), is(KeyManagerFactory.getDefaultAlgorithm())); + assertThat(ssl.keystoreKeyPassword.get(settings).isPresent(), is(false)); + assertThat(ssl.keystorePassword.get(settings).isPresent(), is(false)); + assertThat(ssl.keystorePath.get(settings).isPresent(), is(false)); + assertThat(ssl.supportedProtocols.get(settings).size(), is(0)); + assertThat(ssl.truststoreAlgorithm.get(settings), is(TrustManagerFactory.getDefaultAlgorithm())); + assertThat(ssl.truststorePassword.get(settings).isPresent(), is(false)); + assertThat(ssl.truststorePath.get(settings).isPresent(), is(false)); + assertThat(ssl.verificationMode.get(settings).isPresent(), is(false)); + } + +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java index a484e45ecba..ce7cc61b545 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/ssl/SSLServiceTests.java @@ -37,6 +37,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.Future; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; @@ -236,6 +237,21 @@ public class SSLServiceTests extends ESTestCase { settings.getByPrefix("xpack.security.transport.ssl."))); } + public void testThatHttpClientAuthDefaultsToNone() { + final Settings globalSettings = Settings.builder() + .put("xpack.security.http.ssl.enabled", true) + .put("xpack.ssl.client_authentication", SSLClientAuth.OPTIONAL.name()) + .build(); + final SSLService sslService = new SSLService(globalSettings, env); + + final SSLConfiguration globalConfig = sslService.sslConfiguration(Settings.EMPTY); + assertThat(globalConfig.sslClientAuth(), is(SSLClientAuth.OPTIONAL)); + + final Settings httpSettings = SSLService.getHttpTransportSSLSettings(globalSettings); + final SSLConfiguration httpConfig = sslService.sslConfiguration(httpSettings); + assertThat(httpConfig.sslClientAuth(), is(SSLClientAuth.NONE)); + } + public void testThatTruststorePasswordIsRequired() throws Exception { Settings settings = Settings.builder() .put("xpack.ssl.keystore.path", testnodeStore)