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)