All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.core.ssl.SSLService Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */
package org.elasticsearch.xpack.core.ssl;

import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.IOSession;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.bootstrap.JavaVersion;
import org.elasticsearch.common.CheckedSupplier;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.ssl.DiagnosticTrustManager;
import org.elasticsearch.common.ssl.SslDiagnostics;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.common.socket.SocketAccess;
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
import org.elasticsearch.xpack.core.watcher.WatcherField;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.elasticsearch.xpack.core.XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS;
import static org.elasticsearch.xpack.core.XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING;

/**
 * Provides access to {@link SSLEngine} and {@link SSLSocketFactory} objects based on a provided configuration. All
 * configurations loaded by this service must be configured on construction.
 */
public class SSLService {

    private static final Logger logger = LogManager.getLogger(SSLService.class);
    private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName());

    /**
     * An ordered map of protocol algorithms to SSLContext algorithms. The map is ordered from most
     * secure to least secure. The names in this map are taken from the
     * 
     * Java Security Standard Algorithm Names Documentation for Java 11.
     */
    private static final Map ORDERED_PROTOCOL_ALGORITHM_MAP;
    static {
        LinkedHashMap protocolAlgorithmMap = new LinkedHashMap<>();
        if (DEFAULT_SUPPORTED_PROTOCOLS.contains("TLSv1.3")) {
            protocolAlgorithmMap.put("TLSv1.3", "TLSv1.3");
        }
        protocolAlgorithmMap.put("TLSv1.2", "TLSv1.2");
        protocolAlgorithmMap.put("TLSv1.1", "TLSv1.1");
        protocolAlgorithmMap.put("TLSv1", "TLSv1");
        protocolAlgorithmMap.put("SSLv3", "SSLv3");
        protocolAlgorithmMap.put("SSLv2", "SSL");
        protocolAlgorithmMap.put("SSLv2Hello", "SSL");
        ORDERED_PROTOCOL_ALGORITHM_MAP = Collections.unmodifiableMap(protocolAlgorithmMap);
    }

    private final Settings settings;
    private final boolean diagnoseTrustExceptions;

    /**
     * This is a mapping from "context name" (in general use, the name of a setting key)
     * to a configuration.
     * This allows us to easily answer the question "What is the configuration for ssl in realm XYZ?"
     * Multiple "context names" may map to the same configuration (either by object-identity or by object-equality).
     * For example "xpack.http.ssl" may exist as a name in this map and have the global ssl configuration as a value
     */
    private final Map sslConfigurations;

    /**
     * A mapping from an SSLConfiguration to a pre-built context.
     * 

* This is managed separately to the {@link #sslConfigurations} map, so that a single configuration (by object equality) * always maps to the same {@link SSLContextHolder}, even if it is being used within a different context-name. */ private final Map sslContexts; private final Environment env; /** * Create a new SSLService using the {@code Settings} from {@link Environment#settings()}. * @see #SSLService(Settings, Environment) */ public SSLService(Environment environment) { this(environment, getSSLConfigurations(environment.settings())); } /** * Create a new SSLService using the provided {@link SSLConfiguration} instances. The ssl * contexts created from these configurations will be cached. */ public SSLService(Environment environment, Map sslConfigurations) { this.env = environment; this.settings = environment.settings(); this.diagnoseTrustExceptions = shouldEnableDiagnoseTrust(); this.sslConfigurations = sslConfigurations; this.sslContexts = loadSSLConfigurations(this.sslConfigurations); } /** * Create a new SSLService that parses the settings for the ssl contexts that need to be created, creates them, and then caches them * for use later */ public SSLService(Settings settings, Environment environment) { this.env = environment; this.settings = settings; this.diagnoseTrustExceptions = shouldEnableDiagnoseTrust(); this.sslConfigurations = getSSLConfigurations(this.settings); this.sslContexts = loadSSLConfigurations(this.sslConfigurations); } private SSLService(Settings settings, Environment environment, Map sslConfigurations, Map sslContexts) { this.settings = settings; this.env = environment; this.diagnoseTrustExceptions = shouldEnableDiagnoseTrust(); this.sslConfigurations = sslConfigurations; this.sslContexts = sslContexts; } /** * Creates a new SSLService that supports dynamic creation of SSLContext instances. Instances created by this service will not be * cached and will not be monitored for reloading. This dynamic server does have access to the cached and monitored instances that * have been created during initialization */ public SSLService createDynamicSSLService() { return new SSLService(settings, env, sslConfigurations, sslContexts) { /** * Returns the existing {@link SSLContextHolder} for the configuration * @throws IllegalArgumentException if not found */ @Override SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) { SSLContextHolder holder = sslContexts.get(sslConfiguration); if (holder == null) { // normally we'd throw here but let's create a new one that is not cached and will not be monitored for changes! holder = createSslContext(sslConfiguration); } return holder; } }; } /** * Create a new {@link SSLIOSessionStrategy} based on the provided settings. The settings are used to identify the SSL configuration * that should be used to create the context. * * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return * a context created from the default configuration * @return Never {@code null}. * @deprecated This method will fail if the SSL configuration uses a {@link org.elasticsearch.common.settings.SecureSetting} but the * {@link org.elasticsearch.common.settings.SecureSettings} have been closed. Use {@link #getSSLConfiguration(String)} * and {@link #sslIOSessionStrategy(SSLConfiguration)} (Deprecated, but not removed because monitoring uses dynamic SSL settings) */ @Deprecated public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings) { SSLConfiguration config = sslConfiguration(settings); return sslIOSessionStrategy(config); } public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config) { SSLContext sslContext = sslContext(config); String[] ciphers = supportedCiphers(sslParameters(sslContext).getCipherSuites(), config.cipherSuites(), false); String[] supportedProtocols = config.supportedProtocols().toArray(Strings.EMPTY_ARRAY); HostnameVerifier verifier; if (config.verificationMode().isHostnameVerificationEnabled()) { verifier = SSLIOSessionStrategy.getDefaultHostnameVerifier(); } else { verifier = NoopHostnameVerifier.INSTANCE; } return sslIOSessionStrategy(sslContext, supportedProtocols, ciphers, verifier); } public static HostnameVerifier getHostnameVerifier(SSLConfiguration sslConfiguration) { if (sslConfiguration.verificationMode().isHostnameVerificationEnabled()) { return new DefaultHostnameVerifier(); } else { return NoopHostnameVerifier.INSTANCE; } } /** * The {@link SSLParameters} that are associated with the {@code sslContext}. *

* This method exists to simplify testing since {@link SSLContext#getSupportedSSLParameters()} is {@code final}. * * @param sslContext The SSL context for the current SSL settings * @return Never {@code null}. */ SSLParameters sslParameters(SSLContext sslContext) { return sslContext.getSupportedSSLParameters(); } /** * This method only exists to simplify testing of {@link #sslIOSessionStrategy(Settings)} because {@link SSLIOSessionStrategy} does * not expose any of the parameters that you give it. * * @param sslContext SSL Context used to handle SSL / TCP requests * @param protocols Supported protocols * @param ciphers Supported ciphers * @param verifier Hostname verifier * @return Never {@code null}. */ SSLIOSessionStrategy sslIOSessionStrategy(SSLContext sslContext, String[] protocols, String[] ciphers, HostnameVerifier verifier) { return new SSLIOSessionStrategy(sslContext, protocols, ciphers, verifier) { @Override protected void verifySession(HttpHost host, IOSession iosession, SSLSession session) throws SSLException { if (verifier.verify(host.getHostName(), session) == false) { final Certificate[] certs = session.getPeerCertificates(); final X509Certificate x509 = (X509Certificate) certs[0]; final X500Principal x500Principal = x509.getSubjectX500Principal(); final String altNames = Strings.collectionToCommaDelimitedString(SslDiagnostics.describeValidHostnames(x509)); throw new SSLPeerUnverifiedException(LoggerMessageFormat.format("Expected SSL certificate to be valid for host [{}]," + " but it is only valid for subject alternative names [{}] and subject [{}]", new Object[] { host.getHostName(), altNames, x500Principal.toString() })); } } }; } /** * Create a new {@link SSLSocketFactory} based on the provided configuration. * The socket factory will also properly configure the ciphers and protocols on each socket that is created * @param configuration The SSL configuration to use. Typically obtained from {@link #getSSLConfiguration(String)} * @return Never {@code null}. */ public SSLSocketFactory sslSocketFactory(SSLConfiguration configuration) { final SSLContextHolder contextHolder = sslContextHolder(configuration); SSLSocketFactory socketFactory = contextHolder.sslContext().getSocketFactory(); final SecuritySSLSocketFactory securitySSLSocketFactory = new SecuritySSLSocketFactory( () -> contextHolder.sslContext().getSocketFactory(), configuration.supportedProtocols().toArray(Strings.EMPTY_ARRAY), supportedCiphers(socketFactory.getSupportedCipherSuites(), configuration.cipherSuites(), false)); contextHolder.addReloadListener(securitySSLSocketFactory::reload); return securitySSLSocketFactory; } /** * Creates an {@link SSLEngine} based on the provided configuration. This SSLEngine can be used for a connection that requires * hostname verification assuming the provided * host and port are correct. The SSLEngine created by this method is most useful for clients with hostname verification enabled * * @param configuration the ssl configuration * @param host the host of the remote endpoint. If using hostname verification, this should match what is in the remote * endpoint's certificate * @param port the port of the remote endpoint * @return {@link SSLEngine} * @see #getSSLConfiguration(String) */ public SSLEngine createSSLEngine(SSLConfiguration configuration, String host, int port) { SSLContext sslContext = sslContext(configuration); SSLEngine sslEngine = sslContext.createSSLEngine(host, port); String[] ciphers = supportedCiphers(sslEngine.getSupportedCipherSuites(), configuration.cipherSuites(), false); String[] supportedProtocols = configuration.supportedProtocols().toArray(Strings.EMPTY_ARRAY); SSLParameters parameters = new SSLParameters(ciphers, supportedProtocols); if (configuration.verificationMode().isHostnameVerificationEnabled() && host != null) { // By default, an SSLEngine will not perform hostname verification. In order to perform hostname verification // we need to specify a EndpointIdentificationAlgorithm. We use the HTTPS algorithm to prevent against // man in the middle attacks for all of our connections. parameters.setEndpointIdentificationAlgorithm("HTTPS"); } // we use the cipher suite order so that we can prefer the ciphers we set first in the list parameters.setUseCipherSuitesOrder(true); configuration.sslClientAuth().configure(parameters); // many SSLEngine options can be configured using either SSLParameters or direct methods on the engine itself, but there is one // tricky aspect; if you set a value directly on the engine and then later set the SSLParameters the value set directly on the // engine will be overwritten by the value in the SSLParameters sslEngine.setSSLParameters(parameters); return sslEngine; } /** * Returns whether the provided settings results in a valid configuration that can be used for server connections * * @param sslConfiguration the configuration to check */ public boolean isConfigurationValidForServerUsage(SSLConfiguration sslConfiguration) { Objects.requireNonNull(sslConfiguration, "SSLConfiguration cannot be null"); return sslConfiguration.keyConfig() != KeyConfig.NONE; } /** * Indicates whether client authentication is enabled for a particular configuration */ public boolean isSSLClientAuthEnabled(SSLConfiguration sslConfiguration) { Objects.requireNonNull(sslConfiguration, "SSLConfiguration cannot be null"); return sslConfiguration.sslClientAuth().enabled(); } /** * Returns the {@link SSLContext} for the configuration. Mainly used for testing */ public SSLContext sslContext(SSLConfiguration configuration) { return sslContextHolder(configuration).sslContext(); } public void reloadSSLContext(SSLConfiguration configuration) { sslContextHolder(configuration).reload(); } /** * Returns the existing {@link SSLContextHolder} for the configuration * * @throws IllegalArgumentException if not found */ SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) { Objects.requireNonNull(sslConfiguration, "SSL Configuration cannot be null"); SSLContextHolder holder = sslContexts.get(sslConfiguration); if (holder == null) { throw new IllegalArgumentException("did not find an SSLContext for [" + sslConfiguration.toString() + "]"); } return holder; } /** * Returns the existing {@link SSLConfiguration} for the given settings * * @param settings the settings for the ssl configuration * @return the ssl configuration for the provided settings */ public SSLConfiguration sslConfiguration(Settings settings) { return new SSLConfiguration(settings); } public Set getTransportProfileContextNames() { return Collections.unmodifiableSet(this.sslConfigurations .keySet().stream() .filter(k -> k.startsWith("transport.profiles.")) .collect(Collectors.toSet())); } /** * Accessor to the loaded ssl configuration objects at the current point in time. This is useful for testing */ Collection getLoadedSSLConfigurations() { return Collections.unmodifiableSet(new HashSet<>(sslContexts.keySet())); } /** * Returns the intersection of the supported ciphers with the requested ciphers. This method will also optionally log if unsupported * ciphers were requested. * * @throws IllegalArgumentException if no supported ciphers are in the requested ciphers */ String[] supportedCiphers(String[] supportedCiphers, List requestedCiphers, boolean log) { List supportedCiphersList = new ArrayList<>(requestedCiphers.size()); List unsupportedCiphers = new LinkedList<>(); boolean found; for (String requestedCipher : requestedCiphers) { found = false; for (String supportedCipher : supportedCiphers) { if (supportedCipher.equals(requestedCipher)) { found = true; supportedCiphersList.add(requestedCipher); break; } } if (found == false) { unsupportedCiphers.add(requestedCipher); } } if (supportedCiphersList.isEmpty()) { throw new IllegalArgumentException("none of the ciphers " + Arrays.toString(requestedCiphers.toArray()) + " are supported by this JVM"); } if (log && unsupportedCiphers.isEmpty() == false) { logger.error("unsupported ciphers [{}] were requested but cannot be used in this JVM, however there are supported ciphers " + "that will be used [{}]. If you are trying to use ciphers with a key length greater than 128 bits on an Oracle JVM, " + "you will need to install the unlimited strength JCE policy files.", unsupportedCiphers, supportedCiphersList); } return supportedCiphersList.toArray(new String[supportedCiphersList.size()]); } /** * Creates an {@link SSLContext} based on the provided configuration * * @param sslConfiguration the configuration to use for context creation * @return the created SSLContext */ private SSLContextHolder createSslContext(SSLConfiguration sslConfiguration) { if (logger.isDebugEnabled()) { logger.debug("using ssl settings [{}]", sslConfiguration); } X509ExtendedTrustManager trustManager = sslConfiguration.trustConfig().createTrustManager(env); X509ExtendedKeyManager keyManager = sslConfiguration.keyConfig().createKeyManager(env); return createSslContext(keyManager, trustManager, sslConfiguration); } /** * Creates an {@link SSLContext} based on the provided configuration and trust/key managers * * @param sslConfiguration the configuration to use for context creation * @param keyManager the key manager to use * @param trustManager the trust manager to use * @return the created SSLContext */ private SSLContextHolder createSslContext(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager, SSLConfiguration sslConfiguration) { trustManager = wrapWithDiagnostics(trustManager, sslConfiguration); // Initialize sslContext try { SSLContext sslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols())); sslContext.init(new X509ExtendedKeyManager[]{keyManager}, new X509ExtendedTrustManager[]{trustManager}, null); // check the supported ciphers and log them here to prevent spamming logs on every call supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), true); return new SSLContextHolder(sslContext, sslConfiguration); } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new ElasticsearchException("failed to initialize the SSLContext", e); } } X509ExtendedTrustManager wrapWithDiagnostics(X509ExtendedTrustManager trustManager, SSLConfiguration configuration) { if (diagnoseTrustExceptions && trustManager instanceof DiagnosticTrustManager == false) { final Logger diagnosticLogger = LogManager.getLogger(DiagnosticTrustManager.class); // A single configuration might be used in many place, if there are multiple, we just list "shared" because // that is better than the alternatives. Just listing would be misleading (it might not be the right one) // but listing all of them would be confusing (e.g. some might be the default realms) // This needs to be a supplier (deferred evaluation) because we might load more configurations after this context is built. final Supplier contextName = () -> { final List names = sslConfigurations.entrySet().stream() .filter(e -> e.getValue().equals(configuration)) .limit(2) // we only need to distinguishing between 0/1/many .map(Entry::getKey) .collect(Collectors.toList()); switch (names.size()) { case 0: return "(unknown)"; case 1: return names.get(0); default: return "(shared)"; } }; trustManager = new DiagnosticTrustManager(trustManager, contextName, diagnosticLogger::warn); } return trustManager; } public static Map getSSLConfigurations(Settings settings) { final Map sslSettingsMap = getSSLSettingsMap(settings); final Map sslConfigurationMap = new HashMap<>(sslSettingsMap.size()); sslSettingsMap.forEach((key, sslSettings) -> { if (key.endsWith(".")) { // Drop trailing '.' so that any exception messages are consistent key = key.substring(0, key.length() - 1); } sslConfigurationMap.put(key, new SSLConfiguration(sslSettings)); }); return Collections.unmodifiableMap(sslConfigurationMap); } static Map getSSLSettingsMap(Settings settings) { final Map sslSettingsMap = new HashMap<>(); sslSettingsMap.put(XPackSettings.HTTP_SSL_PREFIX, getHttpTransportSSLSettings(settings)); sslSettingsMap.put("xpack.http.ssl", settings.getByPrefix("xpack.http.ssl.")); sslSettingsMap.putAll(getRealmsSSLSettings(settings)); sslSettingsMap.putAll(getMonitoringExporterSettings(settings)); sslSettingsMap.put(WatcherField.EMAIL_NOTIFICATION_SSL_PREFIX, settings.getByPrefix(WatcherField.EMAIL_NOTIFICATION_SSL_PREFIX)); sslSettingsMap.put(XPackSettings.TRANSPORT_SSL_PREFIX, settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX)); sslSettingsMap.putAll(getTransportProfileSSLSettings(settings)); return Collections.unmodifiableMap(sslSettingsMap); } /** * Parses the settings to load all SSLConfiguration objects that will be used. */ Map loadSSLConfigurations(Map sslConfigurationMap) { final Map sslContextHolders = new HashMap<>(sslConfigurationMap.size()); sslConfigurationMap.forEach((key, sslConfiguration) -> { try { sslContextHolders.computeIfAbsent(sslConfiguration, this::createSslContext); } catch (Exception e) { throw new ElasticsearchSecurityException("failed to load SSL configuration [{}]", e, key); } }); for (String context : Arrays.asList("xpack.security.transport.ssl", "xpack.security.http.ssl")) { validateServerConfiguration(context); } return Collections.unmodifiableMap(sslContextHolders); } private void validateServerConfiguration(String prefix) { assert prefix.endsWith(".ssl"); SSLConfiguration configuration = getSSLConfiguration(prefix); final String enabledSetting = prefix + ".enabled"; if (settings.getAsBoolean(enabledSetting, false)) { // Client Authentication _should_ be required, but if someone turns it off, then this check is no longer relevant final SSLConfigurationSettings configurationSettings = SSLConfigurationSettings.withPrefix(prefix + "."); if (isConfigurationValidForServerUsage(configuration) == false) { deprecationLogger.deprecate(DeprecationCategory.SECURITY, "invalid_ssl_configuration", "invalid SSL configuration for " + prefix + " - server ssl configuration requires a key and certificate, but these have not been configured; you must set either " + "[" + configurationSettings.x509KeyPair.keystorePath.getKey() + "], or both [" + configurationSettings.x509KeyPair.keyPath.getKey() + "] and [" + configurationSettings.x509KeyPair.certificatePath.getKey() + "]"); } } else if (settings.hasValue(enabledSetting) == false) { final List sslSettingNames = settings.keySet().stream() .filter(s -> s.startsWith(prefix)) .sorted() .collect(Collectors.toList()); if (sslSettingNames.isEmpty() == false) { deprecationLogger.deprecate(DeprecationCategory.SECURITY, "invalid_ssl_configuration", "invalid configuration for " + prefix + " - [" + enabledSetting + "] is not set, but the following settings have been configured in elasticsearch.yml : [" + Strings.collectionToCommaDelimitedString(sslSettingNames) + "]"); } } } /** * Returns information about each certificate that is referenced by any SSL configuration. * This includes certificates used for identity (with a private key) and those used for trust, but excludes * certificates that are provided by the JRE. * Due to the nature of KeyStores, this may include certificates that are available, but never used * such as a CA certificate that is no longer in use, or a server certificate for an unrelated host. * * @see TrustConfig#certificates(Environment) */ public Set getLoadedCertificates() throws GeneralSecurityException, IOException { Set certificates = new HashSet<>(); for (SSLConfiguration config : this.getLoadedSSLConfigurations()) { certificates.addAll(config.getDefinedCertificates(env)); } return certificates; } /** * This socket factory wraps an existing SSLSocketFactory and sets the protocols and ciphers on each SSLSocket after it is created. This * is needed even though the SSLContext is configured properly as the configuration does not flow down to the sockets created by the * SSLSocketFactory obtained from the SSLContext. */ private static class SecuritySSLSocketFactory extends SSLSocketFactory { private final Supplier delegateSupplier; private final String[] supportedProtocols; private final String[] ciphers; private volatile SSLSocketFactory delegate; SecuritySSLSocketFactory(Supplier delegateSupplier, String[] supportedProtocols, String[] ciphers) { this.delegateSupplier = delegateSupplier; this.delegate = this.delegateSupplier.get(); this.supportedProtocols = supportedProtocols; this.ciphers = ciphers; } @Override public String[] getDefaultCipherSuites() { return ciphers; } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public Socket createSocket() throws IOException { SSLSocket sslSocket = createWithPermissions(delegate::createSocket); configureSSLSocket(sslSocket); return sslSocket; } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(socket, host, port, autoClose)); configureSSLSocket(sslSocket); return sslSocket; } @Override public Socket createSocket(String host, int port) throws IOException { SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(host, port)); configureSSLSocket(sslSocket); return sslSocket; } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(host, port, localHost, localPort)); configureSSLSocket(sslSocket); return sslSocket; } @Override public Socket createSocket(InetAddress host, int port) throws IOException { SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(host, port)); configureSSLSocket(sslSocket); return sslSocket; } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(address, port, localAddress, localPort)); configureSSLSocket(sslSocket); return sslSocket; } public void reload() { final SSLSocketFactory newDelegate = delegateSupplier.get(); this.delegate = newDelegate; } private void configureSSLSocket(SSLSocket socket) { SSLParameters parameters = new SSLParameters(ciphers, supportedProtocols); // we use the cipher suite order so that we can prefer the ciphers we set first in the list parameters.setUseCipherSuitesOrder(true); socket.setSSLParameters(parameters); } private static SSLSocket createWithPermissions(CheckedSupplier supplier) throws IOException { return (SSLSocket) SocketAccess.doPrivileged(supplier); } } final class SSLContextHolder { private volatile SSLContext context; private final KeyConfig keyConfig; private final TrustConfig trustConfig; private final SSLConfiguration sslConfiguration; private final List reloadListeners; SSLContextHolder(SSLContext context, SSLConfiguration sslConfiguration) { this.context = context; this.sslConfiguration = sslConfiguration; this.keyConfig = sslConfiguration.keyConfig(); this.trustConfig = sslConfiguration.trustConfig(); this.reloadListeners = new ArrayList<>(); } SSLContext sslContext() { return context; } synchronized void reload() { invalidateSessions(context.getClientSessionContext()); invalidateSessions(context.getServerSessionContext()); reloadSslContext(); this.reloadListeners.forEach(Runnable::run); } private void reloadSslContext() { try { X509ExtendedKeyManager loadedKeyManager = keyConfig.createKeyManager(env); X509ExtendedTrustManager loadedTrustManager = trustConfig.createTrustManager(env); loadedTrustManager = wrapWithDiagnostics(loadedTrustManager, sslConfiguration); SSLContext loadedSslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols())); loadedSslContext.init(new X509ExtendedKeyManager[]{loadedKeyManager}, new X509ExtendedTrustManager[]{loadedTrustManager}, null); supportedCiphers(loadedSslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), false); this.context = loadedSslContext; } catch (GeneralSecurityException e) { throw new ElasticsearchException("failed to initialize the SSLContext", e); } } public void addReloadListener(Runnable listener) { this.reloadListeners.add(listener); } } /** * Invalidates the sessions in the provided {@link SSLSessionContext} */ static void invalidateSessions(SSLSessionContext sslSessionContext) { Enumeration sessionIds = sslSessionContext.getIds(); while (sessionIds.hasMoreElements()) { byte[] sessionId = sessionIds.nextElement(); SSLSession session = sslSessionContext.getSession(sessionId); // an SSLSession could be null as there is no lock while iterating, the session cache // could have evicted a value, the session could be timed out, or the session could // have already been invalidated, which removes the value from the session cache in the // sun implementation if (session != null) { session.invalidate(); } } } /** * @return A map of Settings prefix to Settings object */ private static Map getRealmsSSLSettings(Settings settings) { final Map sslSettings = new HashMap<>(); final String prefix = "xpack.security.authc.realms."; final Map settingsByRealmType = settings.getGroups(prefix); settingsByRealmType.forEach((realmType, typeSettings) -> { final Optional nonDottedSetting = typeSettings.keySet().stream().filter(k -> k.indexOf('.') == -1).findAny(); if (nonDottedSetting.isPresent()) { logger.warn("Skipping any SSL configuration from realm [{}{}] because the key [{}] is not in the correct format", prefix, realmType, nonDottedSetting.get()); } else { typeSettings.getAsGroups().forEach((realmName, realmSettings) -> { Settings realmSSLSettings = realmSettings.getByPrefix("ssl."); // Put this even if empty, so that the name will be mapped to the global SSL configuration sslSettings.put(prefix + realmType + "." + realmName + ".ssl", realmSSLSettings); }); } } ); return sslSettings; } private static Map getTransportProfileSSLSettings(Settings settings) { Map sslSettings = new HashMap<>(); Map profiles = settings.getGroups("transport.profiles.", true); for (Entry entry : profiles.entrySet()) { Settings profileSettings = entry.getValue().getByPrefix("xpack.security.ssl."); sslSettings.put("transport.profiles." + entry.getKey() + ".xpack.security.ssl", profileSettings); } return sslSettings; } private static Settings getHttpTransportSSLSettings(Settings settings) { Settings httpSSLSettings = settings.getByPrefix(XPackSettings.HTTP_SSL_PREFIX); if (httpSSLSettings.isEmpty()) { return httpSSLSettings; } Settings.Builder builder = Settings.builder().put(httpSSLSettings); if (builder.get("client_authentication") == null) { builder.put("client_authentication", XPackSettings.HTTP_CLIENT_AUTH_DEFAULT); } return builder.build(); } public SSLConfiguration getHttpTransportSSLConfiguration() { return getSSLConfiguration(XPackSettings.HTTP_SSL_PREFIX); } private static Map getMonitoringExporterSettings(Settings settings) { Map sslSettings = new HashMap<>(); Map exportersSettings = settings.getGroups("xpack.monitoring.exporters."); for (Entry entry : exportersSettings.entrySet()) { Settings exporterSSLSettings = entry.getValue().getByPrefix("ssl."); // Put this even if empty, so that the name will be mapped to the global SSL configuration sslSettings.put("xpack.monitoring.exporters." + entry.getKey() + ".ssl", exporterSSLSettings); } return sslSettings; } public SSLConfiguration getSSLConfiguration(String contextName) { if (contextName.endsWith(".")) { contextName = contextName.substring(0, contextName.length() - 1); } final SSLConfiguration configuration = sslConfigurations.get(contextName); if (configuration == null) { logger.warn("Cannot find SSL configuration for context {}. Known contexts are: {}", contextName, Strings.collectionToCommaDelimitedString(sslConfigurations.keySet())); } return configuration; } /** * Maps the supported protocols to an appropriate ssl context algorithm. We make an attempt to use the "best" algorithm when * possible. The names in this method are taken from the * Java Security * Standard Algorithm Names Documentation for Java 11. */ private static String sslContextAlgorithm(List supportedProtocols) { if (supportedProtocols.isEmpty()) { throw new IllegalArgumentException("no SSL/TLS protocols have been configured"); } for (Entry entry : ORDERED_PROTOCOL_ALGORITHM_MAP.entrySet()) { if (supportedProtocols.contains(entry.getKey())) { return entry.getValue(); } } throw new IllegalArgumentException("no supported SSL/TLS protocol was found in the configured supported protocols: " + supportedProtocols); } private boolean shouldEnableDiagnoseTrust() { // We disable the DiagnosticTrustManager in Java 8 when SunJSSE is set in FIPS 140 mode, as it doesn't allow X509TrustManager to be // wrapped if (inSunJsseInFipsMode()) { logger.info("diagnostic messages for SSL/TLS trust cannot be enabled for SunJSSE in FIPS mode."); return false; } else if (XPackSettings.FIPS_MODE_ENABLED.get(settings) && DIAGNOSE_TRUST_EXCEPTIONS_SETTING.exists(settings) == false) { logger.info("diagnostic messages for SSL/TLS trust failures are not enabled in FIPS 140 mode by default."); return false; } else { return DIAGNOSE_TRUST_EXCEPTIONS_SETTING.get(settings); } } static boolean inSunJsseInFipsMode() { return JavaVersion.current().getVersion().get(0) == 8 && Arrays.stream(Security.getProviders()) .anyMatch(provider -> provider.getName().equals("SunJSSE") && provider.getInfo().contains("FIPS mode")); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy