diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java index c0c30bc916f6f..ec521b196d5a9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java @@ -16,6 +16,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.Version; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.network.CloseableChannel; import org.elasticsearch.common.network.NetworkService; @@ -31,17 +32,20 @@ import org.elasticsearch.xpack.core.security.transport.SSLExceptionHelper; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import static org.elasticsearch.xpack.core.security.SecurityField.setting; @@ -55,6 +59,7 @@ public class SecurityNetty4Transport extends Netty4Transport { private final SSLConfiguration sslConfiguration; private final Map profileConfiguration; private final boolean sslEnabled; + private final Map tlsDeprecation; public SecurityNetty4Transport( final Settings settings, @@ -72,18 +77,42 @@ public SecurityNetty4Transport( this.sslConfiguration = sslService.getSSLConfiguration(setting("transport.ssl.")); Map profileConfiguration = getTransportProfileConfigurations(settings, sslService, sslConfiguration); this.profileConfiguration = Collections.unmodifiableMap(profileConfiguration); + this.tlsDeprecation = buildTlsDeprecationHandlers(settings, profileConfiguration.keySet()); } else { this.profileConfiguration = Collections.emptyMap(); this.sslConfiguration = null; + this.tlsDeprecation = Collections.emptyMap(); } } + // Package protected for testing + static Map buildTlsDeprecationHandlers(Settings settings, Set profileNames) { + TLSv1DeprecationHandler defaultTlsDeprecationHandler = new TLSv1DeprecationHandler(setting("transport.ssl."), settings, logger); + if (defaultTlsDeprecationHandler.shouldLogWarnings()) { + final Map handlers = new HashMap<>(); + profileNames.forEach(name -> { + if (TransportSettings.DEFAULT_PROFILE.equals(name)) { + handlers.put(name, defaultTlsDeprecationHandler); + } else { + handlers.put(name, new TLSv1DeprecationHandler(getTransportProfileSslPrefix(name) + ".", settings, logger)); + } + }); + return Collections.unmodifiableMap(handlers); + } else { + return Collections.emptyMap(); + } + } + + private static String getTransportProfileSslPrefix(String name) { + return "transport.profiles." + name + "." + setting("ssl") ; + } + public static Map getTransportProfileConfigurations(Settings settings, SSLService sslService, SSLConfiguration defaultConfiguration) { Set profileNames = settings.getGroups("transport.profiles.", true).keySet(); Map profileConfiguration = new HashMap<>(profileNames.size() + 1); for (String profileName : profileNames) { - SSLConfiguration configuration = sslService.getSSLConfiguration("transport.profiles." + profileName + "." + setting("ssl")); + SSLConfiguration configuration = sslService.getSSLConfiguration(getTransportProfileSslPrefix(profileName)); profileConfiguration.put(profileName, configuration); } @@ -155,9 +184,13 @@ public void onException(TcpChannel channel, Exception e) { public class SslChannelInitializer extends ServerChannelInitializer { private final SSLConfiguration configuration; - public SslChannelInitializer(String name, SSLConfiguration configuration) { + @Nullable + private final Consumer handshakeListener; + + public SslChannelInitializer(String name, SSLConfiguration configuration, Consumer handshakeListener) { super(name); this.configuration = configuration; + this.handshakeListener = handshakeListener; } @Override @@ -167,11 +200,30 @@ protected void initChannel(Channel ch) throws Exception { serverEngine.setUseClientMode(false); final SslHandler sslHandler = new SslHandler(serverEngine); ch.pipeline().addFirst("sslhandler", sslHandler); + if (handshakeListener != null) { + sslHandler.handshakeFuture().addListener(fut -> { + SSLSession session = serverEngine.getSession(); + handshakeListener.accept(session); + }); + } } } protected ServerChannelInitializer getSslChannelInitializer(final String name, final SSLConfiguration configuration) { - return new SslChannelInitializer(name, sslConfiguration); + return new SslChannelInitializer(name, sslConfiguration, getSslHandshakeListenerForProfile(name)); + } + + @Nullable + protected Consumer getSslHandshakeListenerForProfile(String name) { + // This is handled here (rather than as an interceptor) so we know which profile was used, and can + // provide the correct setting to report on (this may be technically also possible in a transport interceptor, but the + // existing security interceptor doesn't have that now, and adding it would be a more intrusive change). + final TLSv1DeprecationHandler deprecationHandler = tlsDeprecation.get(name); + if (deprecationHandler != null && deprecationHandler.shouldLogWarnings()) { + return session -> deprecationHandler.checkAndLog(session, () -> "transport profile: " + name); + } else { + return null; + } } private class SecurityClientChannelInitializer extends ClientChannelInitializer { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java index d2c809d3ded7e..e8091c89564d7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java @@ -14,7 +14,6 @@ import org.elasticsearch.xpack.core.XPackSettings; import javax.net.ssl.TrustManagerFactory; - import java.security.KeyStore; import java.util.Arrays; import java.util.Collection; @@ -62,7 +61,7 @@ public class SSLConfigurationSettings { public static final Setting> CIPHERS_SETTING_PROFILES = Setting.affixKeySetting("transport.profiles.", "xpack.security.ssl.cipher_suites", CIPHERS_SETTING_TEMPLATE); - private static final Function>> SUPPORTED_PROTOCOLS_TEMPLATE = key -> Setting.listSetting(key, + static final Function>> SUPPORTED_PROTOCOLS_TEMPLATE = key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), propertiesFromKey(key)); public static final Setting> SUPPORTED_PROTOCOLS_PROFILES = Setting.affixKeySetting("transport.profiles.", "xpack.security.ssl.supported_protocols", SUPPORTED_PROTOCOLS_TEMPLATE) ; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java index e8e5f602ebf31..c1d9364388cc0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java @@ -155,18 +155,20 @@ SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) { * * @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 + * @param tlsDeprecationHandler a handler in case TLSv1.0 is used for an SSL connection under this strategy * @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) + * and {@link #sslIOSessionStrategy(SSLConfiguration, TLSv1DeprecationHandler)} + * (Deprecated, but not removed because monitoring uses dynamic SSL settings) */ @Deprecated - public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings) { + public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings, TLSv1DeprecationHandler tlsDeprecationHandler) { SSLConfiguration config = sslConfiguration(settings); - return sslIOSessionStrategy(config); + return sslIOSessionStrategy(config, tlsDeprecationHandler); } - public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config) { + public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config, TLSv1DeprecationHandler tlsDeprecationHandler) { SSLContext sslContext = sslContext(config); String[] ciphers = supportedCiphers(sslParameters(sslContext).getCipherSuites(), config.cipherSuites(), false); String[] supportedProtocols = config.supportedProtocols().toArray(Strings.EMPTY_ARRAY); @@ -177,10 +179,24 @@ public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config) { } else { verifier = NoopHostnameVerifier.INSTANCE; } + verifier = wrapHostnameVerifier(verifier, tlsDeprecationHandler); return sslIOSessionStrategy(sslContext, supportedProtocols, ciphers, verifier); } + public HostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier, TLSv1DeprecationHandler tlsDeprecationHandler) { + // Using the hostname verifier for this is just ugly, but HTTP client doesn't expose the SSLSession in many places + // and this is the easiest one to hook into in a non-intrusive way + if (tlsDeprecationHandler.shouldLogWarnings()) { + return (hostname, session) -> { + tlsDeprecationHandler.checkAndLog(session, () -> "http connection to " + hostname); + return verifier.verify(hostname, session); + }; + } else { + return verifier; + } + } + /** * The {@link SSLParameters} that are associated with the {@code sslContext}. *

@@ -194,8 +210,8 @@ SSLParameters sslParameters(SSLContext sslContext) { } /** - * 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. + * This method only exists to simplify testing of {@link #sslIOSessionStrategy(Settings, TLSv1DeprecationHandler)} 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 diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandler.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandler.java new file mode 100644 index 0000000000000..caa51498e9370 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandler.java @@ -0,0 +1,72 @@ +/* + * 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.core.ssl; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.settings.Settings; + +import javax.net.ssl.SSLSession; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.function.Supplier; + +import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.SUPPORTED_PROTOCOLS_TEMPLATE; + +/** + * Handles logging deprecation warnings when a TLSv1.0 SSL connection is used, and that SSL context relies on + * the default list of supported_protocols (in Elasticsearch 7.0, this list will not include TLS 1.0). + */ +public class TLSv1DeprecationHandler { + + private final String supportedProtocolsSetting; + private final boolean shouldLogWarnings; + private final DeprecationLogger deprecationLogger; + + public TLSv1DeprecationHandler(String settingPrefix, Settings settings, Logger baseLogger) { + if (settingPrefix.length() > 0 && settingPrefix.endsWith("ssl.") == false) { + throw new IllegalArgumentException("Setting prefix [" + settingPrefix + "] must end in 'ssl.'"); + } + this.supportedProtocolsSetting = settingPrefix + "supported_protocols"; + this.shouldLogWarnings = SUPPORTED_PROTOCOLS_TEMPLATE.apply(supportedProtocolsSetting).exists(settings) == false; + if (shouldLogWarnings) { + deprecationLogger = new DeprecationLogger(baseLogger); + } else { + deprecationLogger = null; + } + } + + private TLSv1DeprecationHandler(String settingKey, boolean shouldLog, DeprecationLogger logger) { + this.supportedProtocolsSetting = settingKey; + this.shouldLogWarnings = shouldLog; + this.deprecationLogger = logger; + } + + public static TLSv1DeprecationHandler disabled() { + return new TLSv1DeprecationHandler(null, false, null); + } + + public boolean shouldLogWarnings() { + return shouldLogWarnings; + } + + public void checkAndLog(SSLSession session, Supplier descriptionSupplier) { + if (shouldLogWarnings == false) { + return; + } + if ("TLSv1".equals(session.getProtocol())) { + final String description = descriptionSupplier.get(); + // Use a "LRU" key that is unique per day. That way each description (source address, etc) will be logged once per day. + final String key = LocalDate.now(ZoneId.of("UTC")) + ":" + description; + deprecationLogger.deprecatedAndMaybeLog(key, + "a TLS v1.0 session was used for [{}], " + + "this protocol will be disabled by default in a future version. " + + "The [{}] setting can be used to control this.", + description, supportedProtocolsSetting); + } + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java index 516d695db85f2..5d0950c287d2d 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java @@ -10,8 +10,8 @@ import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.common.Strings; @@ -20,6 +20,7 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.mocksocket.MockHttpServer; +import org.elasticsearch.xpack.core.XPackSettings; import javax.net.ssl.SSLContext; import java.io.Closeable; @@ -55,6 +56,7 @@ public class MockWebServer implements Closeable { private final Logger logger; private final SSLContext sslContext; private final boolean needClientAuth; + private final List supportedProtocols; private final Set latches = ConcurrentCollections.newConcurrentSet(); private String hostname; private int port; @@ -72,9 +74,20 @@ public MockWebServer() { * @param needClientAuth Should clientAuth be used, which requires a client side certificate */ public MockWebServer(SSLContext sslContext, boolean needClientAuth) { + this(sslContext, needClientAuth, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS); + } + + /** + * Instantiates a webserver with https + * @param sslContext The SSL context to be used for encryption + * @param needClientAuth Should clientAuth be used, which requires a client side certificate + * @param supportedProtocols Which SSL/TLS protocols version should be supported + */ + public MockWebServer(SSLContext sslContext, boolean needClientAuth, List supportedProtocols) { this.needClientAuth = needClientAuth; this.logger = LogManager.getLogger(this.getClass()); this.sslContext = sslContext; + this.supportedProtocols = supportedProtocols; } /** @@ -87,7 +100,7 @@ public void start() throws IOException { InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0); if (sslContext != null) { HttpsServer httpsServer = MockHttpServer.createHttps(address, 0); - httpsServer.setHttpsConfigurator(new CustomHttpsConfigurator(sslContext, needClientAuth)); + httpsServer.setHttpsConfigurator(new CustomHttpsConfigurator(sslContext, needClientAuth, supportedProtocols)); server = httpsServer; } else { server = MockHttpServer.createHttp(address, 0); @@ -144,15 +157,18 @@ public void start() throws IOException { private static final class CustomHttpsConfigurator extends HttpsConfigurator { private final boolean needClientAuth; + private final List supportedProtocols; - CustomHttpsConfigurator(SSLContext sslContext, boolean needClientAuth) { + CustomHttpsConfigurator(SSLContext sslContext, boolean needClientAuth, List supportedProtocols) { super(sslContext); this.needClientAuth = needClientAuth; + this.supportedProtocols = supportedProtocols; } @Override public void configure(HttpsParameters params) { params.setNeedClientAuth(needClientAuth); + params.setProtocols(this.supportedProtocols.toArray(Strings.EMPTY_ARRAY)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4TransportTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4TransportTests.java index 6ce7e2aebdef8..a3c94a14414e4 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4TransportTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4TransportTests.java @@ -6,18 +6,31 @@ package org.elasticsearch.xpack.core.security.transport.netty4; +import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.core.ssl.VerificationMode; import org.hamcrest.Matchers; +import javax.net.ssl.SSLSession; import java.util.Map; +import java.util.Set; import static org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport.getTransportProfileConfigurations; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class SecurityNetty4TransportTests extends ESTestCase { @@ -55,4 +68,52 @@ public void testGetInsecureTransportProfileConfigurations() { assertThat(profileConfigurations.get("none").verificationMode(), Matchers.equalTo(VerificationMode.NONE)); assertThat(profileConfigurations.get("default"), Matchers.sameInstance(defaultConfig)); } + + public void testBuildTlsDeprecationHandlers() { + final Settings settings = Settings.builder() + .putList("transport.profiles.a.xpack.security.ssl.supported_protocols", "TLSv1.2") + .put("transport.profiles.b.xpack.security.ssl.enabled", true) + .put("transport.profiles.c.port", 9393) + .build(); + final Set profiles = Sets.newHashSet(TransportSettings.DEFAULT_PROFILE, "a", "b", "c"); + final Map handlers = SecurityNetty4Transport.buildTlsDeprecationHandlers(settings, profiles); + + assertThat(handlers.keySet(), containsInAnyOrder(TransportSettings.DEFAULT_PROFILE, "a", "b", "c")); + assertThat(handlers.get(TransportSettings.DEFAULT_PROFILE).shouldLogWarnings(), is(true)); + assertThat(handlers.get("a").shouldLogWarnings(), is(false)); + assertThat(handlers.get("b").shouldLogWarnings(), is(true)); + assertThat(handlers.get("c").shouldLogWarnings(), is(true)); + + assertTlsDeprecationWarning(handlers.get(TransportSettings.DEFAULT_PROFILE), true, "xpack.security.transport.ssl."); + assertTlsDeprecationWarning(handlers.get("a"), false, "transport.profiles.a.xpack.security.ssl."); + assertTlsDeprecationWarning(handlers.get("b"), true, "transport.profiles.b.xpack.security.ssl."); + assertTlsDeprecationWarning(handlers.get("c"), true, "transport.profiles.c.xpack.security.ssl."); + } + + private void assertTlsDeprecationWarning(TLSv1DeprecationHandler handler, boolean expectWarning, String settingPrefix) { + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + DeprecationLogger.setThreadContext(threadContext); + try { + final SSLSession sslSession = mock(SSLSession.class); + + when(sslSession.getProtocol()).thenReturn(randomFrom("TLSv1.1", "TLSv1.2")); + handler.checkAndLog(sslSession, () -> "No warning expected"); + assertThat(threadContext.getResponseHeaders(), not(hasKey("Warning"))); + + when(sslSession.getProtocol()).thenReturn("TLSv1"); + handler.checkAndLog(sslSession, () -> "Test TLSv1"); + if (expectWarning) { + assertThat(threadContext.getResponseHeaders(), hasKey("Warning")); + + assertWarnings("a TLS v1.0 session was used for [Test TLSv1]," + + " this protocol will be disabled by default in a future version." + + " The [" + settingPrefix + "supported_protocols] setting can be used to control this."); + } else { + assertThat(threadContext.getResponseHeaders(), not(hasKey("Warning"))); + } + } finally { + DeprecationLogger.removeThreadContext(threadContext); + } + + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java index 0185da5967084..f792e31dddc37 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java @@ -74,6 +74,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -478,6 +479,7 @@ public void testSSLStrategy() { List requestedCiphers = new ArrayList<>(0); ArgumentCaptor verifier = ArgumentCaptor.forClass(HostnameVerifier.class); SSLIOSessionStrategy sslStrategy = mock(SSLIOSessionStrategy.class); + TLSv1DeprecationHandler deprecationHandler = TLSv1DeprecationHandler.disabled(); when(sslService.sslConfiguration(settings)).thenReturn(sslConfig); when(sslService.sslContext(sslConfig)).thenReturn(sslContext); @@ -487,10 +489,11 @@ public void testSSLStrategy() { when(sslService.sslIOSessionStrategy(eq(sslContext), eq(protocols), eq(ciphers), verifier.capture())).thenReturn(sslStrategy); // ensure it actually goes through and calls the real method - when(sslService.sslIOSessionStrategy(settings)).thenCallRealMethod(); - when(sslService.sslIOSessionStrategy(sslConfig)).thenCallRealMethod(); + when(sslService.sslIOSessionStrategy(settings, deprecationHandler)).thenCallRealMethod(); + when(sslService.sslIOSessionStrategy(sslConfig, deprecationHandler)).thenCallRealMethod(); + when(sslService.wrapHostnameVerifier(any(HostnameVerifier.class), eq(deprecationHandler))).thenCallRealMethod(); - assertThat(sslService.sslIOSessionStrategy(settings), sameInstance(sslStrategy)); + assertThat(sslService.sslIOSessionStrategy(settings, deprecationHandler), sameInstance(sslStrategy)); if (mode.isHostnameVerificationEnabled()) { assertThat(verifier.getValue(), instanceOf(DefaultHostnameVerifier.class)); @@ -774,7 +777,7 @@ public void testThatSSLIOSessionStrategyWithoutSettingsWorks() throws Exception SSLService sslService = new SSLService(Settings.EMPTY, env); SSLConfiguration sslConfiguration = globalConfiguration(sslService); logger.info("SSL Configuration: {}", sslConfiguration); - SSLIOSessionStrategy sslStrategy = sslService.sslIOSessionStrategy(sslConfiguration); + SSLIOSessionStrategy sslStrategy = sslService.sslIOSessionStrategy(sslConfiguration, TLSv1DeprecationHandler.disabled()); try (CloseableHttpAsyncClient client = getAsyncHttpClient(sslStrategy)) { client.start(); @@ -794,7 +797,8 @@ public void testThatSSLIOSessionStrategyTrustsJDKTrustedCAs() throws Exception { .setSecureSettings(secureSettings) .build(); final SSLService sslService = new SSLService(settings, env); - SSLIOSessionStrategy sslStrategy = sslService.sslIOSessionStrategy(globalConfiguration(sslService)); + final TLSv1DeprecationHandler deprecationHandler = TLSv1DeprecationHandler.disabled(); + SSLIOSessionStrategy sslStrategy = sslService.sslIOSessionStrategy(globalConfiguration(sslService), deprecationHandler); try (CloseableHttpAsyncClient client = getAsyncHttpClient(sslStrategy)) { client.start(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandlerTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandlerTests.java new file mode 100644 index 0000000000000..9523b3e1e3e9c --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/TLSv1DeprecationHandlerTests.java @@ -0,0 +1,79 @@ +/* + * 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.core.ssl; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import javax.net.ssl.SSLSession; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TLSv1DeprecationHandlerTests extends ESTestCase { + + public void testWithDefaultTlsProtocolsAndTLSv1() { + final String prefix = randomAlphaOfLengthBetween(4, 8) + ".ssl."; + final Settings.Builder builder = Settings.builder(); + builder.put(prefix + "enabled", true); + final TLSv1DeprecationHandler handler = new TLSv1DeprecationHandler(prefix, builder.build(), logger); + assertThat(handler.shouldLogWarnings(), is(true)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn("TLSv1"); + handler.checkAndLog(sslSession, () -> "TLS deprecation test"); + + assertWarnings("a TLS v1.0 session was used for [TLS deprecation test]," + + " this protocol will be disabled by default in a future version." + + " The [" + prefix + "supported_protocols] setting can be used to control this."); + } + + public void testWithCustomisedTlsProtocolsAndTLSv1() { + final String prefix = randomAlphaOfLengthBetween(4, 8) + ".ssl."; + final Settings.Builder builder = Settings.builder(); + builder.put(prefix + "enabled", true); + builder.putList(prefix + "supported_protocols", randomSubsetOf(randomIntBetween(1, 3), "TLSv1", "TLSv1.1", "TLSv1.2")); + final TLSv1DeprecationHandler handler = new TLSv1DeprecationHandler(prefix, builder.build(), logger); + assertThat(handler.shouldLogWarnings(), is(false)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn("TLSv1"); + handler.checkAndLog(sslSession, () -> "TLS deprecation test"); + + // The test base class will enforce that there are no deprecation warnings + } + + public void testWithDefaultTlsProtocolsAndRecentTLS() { + final String prefix = randomAlphaOfLengthBetween(4, 8) + ".ssl."; + final Settings.Builder builder = Settings.builder(); + builder.put(prefix + "enabled", true); + final TLSv1DeprecationHandler handler = new TLSv1DeprecationHandler(prefix, builder.build(), logger); + assertThat(handler.shouldLogWarnings(), is(true)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn(randomFrom("TLSv1.1", "TLSv1.2")); + handler.checkAndLog(sslSession, () -> "TLS deprecation test"); + + // The test base class will enforce that there are no deprecation warnings + } + + public void testWithCustomisedTlsProtocolsAndRecentTLS() { + final String prefix = randomAlphaOfLengthBetween(4, 8) + ".ssl."; + final Settings.Builder builder = Settings.builder(); + builder.put(prefix + "enabled", true); + builder.putList(prefix + "supported_protocols", randomSubsetOf(randomIntBetween(1, 3), "TLSv1", "TLSv1.1", "TLSv1.2")); + final TLSv1DeprecationHandler handler = new TLSv1DeprecationHandler(prefix, builder.build(), logger); + assertThat(handler.shouldLogWarnings(), is(false)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn(randomFrom("TLSv1.1", "TLSv1.2")); + handler.checkAndLog(sslSession, () -> "TLS deprecation test"); + + // The test base class will enforce that there are no deprecation warnings + } +} diff --git a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java index 6ff10cf5711c5..362f218ecdbbc 100644 --- a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java +++ b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java @@ -38,6 +38,7 @@ import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.monitoring.exporter.ClusterAlertsUtil; import org.elasticsearch.xpack.monitoring.exporter.ExportBulk; import org.elasticsearch.xpack.monitoring.exporter.Exporter; @@ -475,17 +476,19 @@ private static void configureHeaders(final RestClientBuilder builder, final Conf private static void configureSecurity(final RestClientBuilder builder, final Config config, final SSLService sslService) { final Setting concreteSetting = SSL_SETTING.getConcreteSettingForNamespace(config.name()); final Settings sslSettings = concreteSetting.get(config.settings()); + final TLSv1DeprecationHandler tlsDeprecationHandler = new TLSv1DeprecationHandler(concreteSetting.getKey(), config.settings(), + logger); final SSLIOSessionStrategy sslStrategy; if (SSLConfigurationSettings.withoutPrefix().getSecureSettingsInUse(sslSettings).isEmpty()) { // This configuration does not use secure settings, so it is possible that is has been dynamically updated. // We need to load a new SSL strategy in case these settings differ from the ones that the SSL service was configured with. - sslStrategy = sslService.sslIOSessionStrategy(sslSettings); + sslStrategy = sslService.sslIOSessionStrategy(sslSettings, tlsDeprecationHandler); } else { // This configuration uses secure settings. We cannot load a new SSL strategy, as the secure settings have already been closed. // Due to #registerSettingValidators we know that the settings not been dynamically updated, and the pre-configured strategy // is still the correct configuration for use in this exporter. final SSLConfiguration sslConfiguration = sslService.getSSLConfiguration(concreteSetting.getKey()); - sslStrategy = sslService.sslIOSessionStrategy(sslConfiguration); + sslStrategy = sslService.sslIOSessionStrategy(sslConfiguration, tlsDeprecationHandler); } final CredentialsProvider credentialsProvider = createCredentialsProvider(config); List hostList = HOST_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings()); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java index 9c036a53f9e6d..f633de6c0a4c0 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.monitoring.exporter.MonitoringTemplateUtils; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.monitoring.exporter.ClusterAlertsUtil; import org.elasticsearch.xpack.monitoring.exporter.ExportBulk; import org.elasticsearch.xpack.monitoring.exporter.Exporter.Config; @@ -201,7 +202,7 @@ public void testExporterWithInvalidHost() { public void testExporterWithUnknownBlacklistedClusterAlerts() { final SSLIOSessionStrategy sslStrategy = mock(SSLIOSessionStrategy.class); - when(sslService.sslIOSessionStrategy(any(Settings.class))).thenReturn(sslStrategy); + when(sslService.sslIOSessionStrategy(any(Settings.class), any(TLSv1DeprecationHandler.class))).thenReturn(sslStrategy); final List blacklist = new ArrayList<>(); blacklist.add("does_not_exist"); @@ -229,7 +230,7 @@ public void testExporterWithUnknownBlacklistedClusterAlerts() { public void testExporterWithHostOnly() throws Exception { final SSLIOSessionStrategy sslStrategy = mock(SSLIOSessionStrategy.class); - when(sslService.sslIOSessionStrategy(any(Settings.class))).thenReturn(sslStrategy); + when(sslService.sslIOSessionStrategy(any(Settings.class), any(TLSv1DeprecationHandler.class))).thenReturn(sslStrategy); final Settings.Builder builder = Settings.builder() .put("xpack.monitoring.exporters._http.type", "http") @@ -243,7 +244,7 @@ public void testExporterWithHostOnly() throws Exception { public void testCreateRestClient() throws IOException { final SSLIOSessionStrategy sslStrategy = mock(SSLIOSessionStrategy.class); - when(sslService.sslIOSessionStrategy(any(Settings.class))).thenReturn(sslStrategy); + when(sslService.sslIOSessionStrategy(any(Settings.class), any(TLSv1DeprecationHandler.class))).thenReturn(sslStrategy); final Settings.Builder builder = Settings.builder() .put("xpack.monitoring.exporters._http.type", "http") diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTlsV1DeprecationTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTlsV1DeprecationTests.java new file mode 100644 index 0000000000000..f64fb624be32f --- /dev/null +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTlsV1DeprecationTests.java @@ -0,0 +1,155 @@ +/* + * 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.monitoring.exporter.http; + +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.ssl.SSLConfiguration; +import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; +import org.elasticsearch.xpack.monitoring.exporter.Exporter; +import org.junit.After; +import org.junit.Before; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; + +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class HttpExporterTlsV1DeprecationTests extends ESTestCase { + + private final ClusterService clusterService = mock(ClusterService.class); + private final XPackLicenseState licenseState = mock(XPackLicenseState.class); + private final SSLService sslService = mock(SSLService.class); + private final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + + @Before + public void setupMocks() { + final ClusterState clusterState = mock(ClusterState.class); + final DiscoveryNodes nodes = mock(DiscoveryNodes.class); + final MetaData metaData = mock(MetaData.class); + + when(clusterService.state()).thenReturn(clusterState); + when(clusterState.metaData()).thenReturn(metaData); + when(clusterState.nodes()).thenReturn(nodes); + // always let the watcher resources run for these tests; HttpExporterResourceTests tests it flipping on/off + when(nodes.isLocalNodeElectedMaster()).thenReturn(true); + + DeprecationLogger.setThreadContext(threadContext); + } + + @After + public void clearThreadContext() { + DeprecationLogger.removeThreadContext(threadContext); + } + + public void testDeprecationWarningEnabledWhenUsingDefaultProtocols() throws IOException { + final Settings.Builder builder = Settings.builder() + .put("xpack.monitoring.exporters._http.type", "http") + .put("xpack.monitoring.exporters._http.host", "https://localhost:12345") + .put("xpack.monitoring.exporters._http.ssl.enabled", true); + + final boolean useSecureSettings = randomBoolean(); + if (useSecureSettings) { + // The HTTP Exporter behaves slightly differently if secure settings are in use (because they cannot be dynamically reloaded) + final MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString("xpack.monitoring.exporters._http.ssl.truststore.secure_password", "random"); + builder.setSecureSettings(secureSettings); + } + + final TLSv1DeprecationHandler deprecationHandler = buildExporterAndGetDeprecationHandler(builder, useSecureSettings); + assertThat(deprecationHandler.shouldLogWarnings(), is(true)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn("TLSv1"); + deprecationHandler.checkAndLog(sslSession, () -> "http exporter test"); + assertThat(threadContext.getResponseHeaders(), hasKey("Warning")); + threadContext.stashContext(); + + when(sslSession.getProtocol()).thenReturn(randomFrom("TLSv1.1", "TLSv1.2")); + deprecationHandler.checkAndLog(sslSession, () -> "http exporter test"); + assertThat(threadContext.getResponseHeaders(), not(hasKey("Warning"))); + + assertWarnings("a TLS v1.0 session was used for [http exporter test]," + + " this protocol will be disabled by default in a future version." + + " The [xpack.monitoring.exporters._http.ssl.supported_protocols] setting can be used to control this."); + } + + public void testNoDeprecationWarningWhenUsingExplicitProtocols() throws IOException { + final Settings.Builder builder = Settings.builder() + .put("xpack.monitoring.exporters._http.type", "http") + .put("xpack.monitoring.exporters._http.host", "https://localhost:12345") + .put("xpack.monitoring.exporters._http.ssl.enabled", true) + .putList("xpack.monitoring.exporters._http.ssl.supported_protocols", "TLSv1.2", "TLSv1.1", "TLSv1"); + + final boolean useSecureSettings = randomBoolean(); + if (useSecureSettings) { + // The HTTP Exporter behaves slightly differently if secure settings are in use (because they cannot be dynamically reloaded) + final MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString("xpack.monitoring.exporters._http.ssl.truststore.secure_password", "not user"); + builder.setSecureSettings(secureSettings); + } + + final TLSv1DeprecationHandler deprecationHandler = buildExporterAndGetDeprecationHandler(builder, useSecureSettings); + assertThat(deprecationHandler.shouldLogWarnings(), is(false)); + + final SSLSession sslSession = mock(SSLSession.class); + when(sslSession.getProtocol()).thenReturn("TLSv1"); + deprecationHandler.checkAndLog(sslSession, () -> "http exporter test"); + assertThat(threadContext.getResponseHeaders(), not(hasKey("Warning"))); + } + + private TLSv1DeprecationHandler buildExporterAndGetDeprecationHandler(Settings.Builder builder, boolean withSecureSettings) + throws IOException { + + final AtomicReference deprecationHandlerRef = new AtomicReference<>(); + final Answer answer = inv -> { + assert inv.getArguments().length == 2; + final TLSv1DeprecationHandler handler = (TLSv1DeprecationHandler) inv.getArguments()[1]; + deprecationHandlerRef.set(handler); + return SSLIOSessionStrategy.getDefaultStrategy(); + }; + if (withSecureSettings) { + Mockito.when(sslService.sslIOSessionStrategy(any(SSLConfiguration.class), any(TLSv1DeprecationHandler.class))).then(answer); + } else { + Mockito.when(sslService.sslIOSessionStrategy(any(Settings.class), any(TLSv1DeprecationHandler.class))).then(answer); + } + + final Exporter.Config config = createConfig(builder.build()); + final NodeFailureListener listener = mock(NodeFailureListener.class); + try (RestClient client = HttpExporter.createRestClient(config, sslService, listener)) { + assertThat(client, notNullValue()); + } + final TLSv1DeprecationHandler deprecationHandler = deprecationHandlerRef.get(); + assertThat(deprecationHandler, notNullValue()); + return deprecationHandler; + } + + private Exporter.Config createConfig(final Settings settings) { + return new Exporter.Config("_http", HttpExporter.TYPE, settings, clusterService, licenseState); + } + +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 269b2b096fd30..d307aadf5a5d2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -133,6 +133,7 @@ import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.TLSLicenseBootstrapCheck; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction; import org.elasticsearch.xpack.core.ssl.action.TransportGetCertificateInfoAction; import org.elasticsearch.xpack.core.ssl.rest.RestGetCertificateInfoAction; @@ -254,6 +255,7 @@ import static java.util.Collections.singletonList; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING; import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED; +import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_PREFIX; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_INDEX_FORMAT; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_TEMPLATE_NAME; @@ -1031,7 +1033,10 @@ public UnaryOperator getRestHandlerWrapper(ThreadContext threadCont final boolean ssl = HTTP_SSL_ENABLED.get(settings); final SSLConfiguration httpSSLConfig = getSslService().getHttpTransportSSLConfiguration(); boolean extractClientCertificate = ssl && getSslService().isSSLClientAuthEnabled(httpSSLConfig); - return handler -> new SecurityRestFilter(getLicenseState(), threadContext, authcService.get(), handler, extractClientCertificate); + final TLSv1DeprecationHandler tlsDeprecationHandler = ssl ? + new TLSv1DeprecationHandler(HTTP_SSL_PREFIX, settings, LOGGER) : TLSv1DeprecationHandler.disabled(); + return handler -> new SecurityRestFilter(getLicenseState(), threadContext, authcService.get(), handler, extractClientCertificate, + tlsDeprecationHandler); } @Override diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index 78f8c68f12459..6997771d02b3a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -10,8 +10,9 @@ import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.util.ssl.HostNameSSLSocketVerifier; -import org.apache.logging.log4j.Logger; +import com.unboundid.util.ssl.SSLSocketVerifier; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.DeprecationLogger; @@ -25,8 +26,10 @@ import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -161,9 +164,20 @@ protected static LDAPConnectionOptions connectionOptions(RealmConfig config, } else { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); } + addTls1DeprecationChecks(options, config, logger); return options; } + static void addTls1DeprecationChecks(LDAPConnectionOptions options, RealmConfig realmConfig, Logger logger) { + final String prefix = RealmSettings.getFullSettingKey(realmConfig, "ssl."); + final TLSv1DeprecationHandler deprecationHandler = new TLSv1DeprecationHandler(prefix, realmConfig.globalSettings(), logger); + if (deprecationHandler.shouldLogWarnings()) { + final SSLSocketVerifier existingVerifier = options.getSSLSocketVerifier(); + assert existingVerifier != null : "LDAPConnectionOptions has null verifier"; + options.setSSLSocketVerifier(new TlsDeprecationSocketVerifier(deprecationHandler, existingVerifier)); + } + } + private LDAPServers ldapServers(Settings settings) { // Parse LDAP urls List ldapUrls = settings.getAsList(SessionFactorySettings.URLS_SETTING, getDefaultLdapUrls(settings)); @@ -261,4 +275,24 @@ private boolean secureUrls(String[] ldapUrls) { return allSecure; } } + + static class TlsDeprecationSocketVerifier extends SSLSocketVerifier { + private final TLSv1DeprecationHandler deprecationHandler; + private final SSLSocketVerifier delegate; + + TlsDeprecationSocketVerifier(TLSv1DeprecationHandler deprecationHandler, SSLSocketVerifier delegate) { + this.deprecationHandler = deprecationHandler; + this.delegate = delegate; + } + + @Override + public void verifySSLSocket(String host, int port, SSLSocket sslSocket) throws LDAPException { + deprecationHandler.checkAndLog(sslSocket.getSession(), () -> "ldap host " + host); + delegate.verifySSLSocket(host, port, sslSocket); + } + + SSLSocketVerifier getDelegate() { + return delegate; + } + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java index ae7500a4a0c09..c9948d24df275 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java @@ -50,6 +50,7 @@ import org.elasticsearch.xpack.core.ssl.CertParsingUtils; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.core.ssl.X509KeyPairSettings; import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.authc.TokenService; @@ -529,8 +530,10 @@ private static Tuple "HTTP connection from " + remoteHost(request)); + } + if (licenseState.isAuthAllowed() && request.method() != Method.OPTIONS) { // CORS - allow for preflight unauthenticated OPTIONS request if (extractClientCertificate) { Netty4HttpRequest nettyHttpRequest = (Netty4HttpRequest) request; - SslHandler handler = nettyHttpRequest.getChannel().pipeline().get(SslHandler.class); - assert handler != null; - ServerTransportFilter.extractClientCertificates(logger, threadContext, handler.engine(), nettyHttpRequest.getChannel()); + final SSLEngine engine = getSslEngine(nettyHttpRequest); + ServerTransportFilter.extractClientCertificates(logger, threadContext, engine, nettyHttpRequest.getChannel()); } service.authenticate(maybeWrapRestRequest(request), ActionListener.wrap( authentication -> { @@ -73,6 +88,22 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } } + private String remoteHost(RestRequest request) { + final SocketAddress address = request.getRemoteAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress inet = (InetSocketAddress) address; + return inet.getHostString(); + } else { + return address.toString(); + } + } + + private SSLEngine getSslEngine(Netty4HttpRequest nettyHttpRequest) { + SslHandler handler = nettyHttpRequest.getChannel().pipeline().get(SslHandler.class); + assert handler != null; + return handler.engine(); + } + RestRequest maybeWrapRestRequest(RestRequest restRequest) throws IOException { if (restHandler instanceof RestRequestFilter) { return ((RestRequestFilter)restHandler).getFilteredRequest(restRequest); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4ServerTransport.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4ServerTransport.java index 8cb1085d3aace..0dea0642189c3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4ServerTransport.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4ServerTransport.java @@ -20,6 +20,9 @@ import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; +import javax.net.ssl.SSLSession; +import java.util.function.Consumer; + public class SecurityNetty4ServerTransport extends SecurityNetty4Transport { @Nullable private final IPFilter authenticator; @@ -53,7 +56,7 @@ protected ChannelHandler getNoSslChannelInitializer(final String name) { @Override protected ServerChannelInitializer getSslChannelInitializer(final String name, final SSLConfiguration configuration) { - return new SecurityServerChannelInitializer(name, configuration); + return new SecurityServerChannelInitializer(name, configuration, getSslHandshakeListenerForProfile(name)); } public class IPFilterServerChannelInitializer extends ServerChannelInitializer { @@ -71,8 +74,8 @@ protected void initChannel(final Channel ch) throws Exception { public class SecurityServerChannelInitializer extends SslChannelInitializer { - SecurityServerChannelInitializer(final String name, final SSLConfiguration configuration) { - super(name, configuration); + SecurityServerChannelInitializer(final String name, final SSLConfiguration configuration, Consumer handshakeListener) { + super(name, configuration, handshakeListener); } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java index 2ca1463637641..ec29d1c1e0f6c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java @@ -9,7 +9,6 @@ import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.SimpleBindRequest; - import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -79,13 +78,13 @@ public void testBindWithReadTimeout() throws Exception { String userTemplates = "cn={0},ou=people,o=sevenSeas"; Settings settings = Settings.builder() - .put(buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE)) - .put(SessionFactorySettings.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond - .put("path.home", createTempDir()) - .build(); + .put(buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE)) + .put(SessionFactorySettings.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond + .put("path.home", createTempDir()) + .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings, - TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService, threadPool); String user = "Horatio Hornblower"; SecureString userPass = new SecureString("pass"); @@ -93,7 +92,7 @@ public void testBindWithReadTimeout() throws Exception { ldapServer.setProcessingDelayMillis(500L); try { UncategorizedExecutionException e = - expectThrows(UncategorizedExecutionException.class, () -> session(sessionFactory, user, userPass)); + expectThrows(UncategorizedExecutionException.class, () -> session(sessionFactory, user, userPass)); assertThat(e.getCause(), instanceOf(ExecutionException.class)); assertThat(e.getCause().getCause(), instanceOf(LDAPException.class)); assertThat(e.getCause().getCause().getMessage(), containsString("A client-side timeout was encountered while waiting ")); @@ -104,14 +103,14 @@ public void testBindWithReadTimeout() throws Exception { public void testBindWithTemplates() throws Exception { String groupSearchBase = "o=sevenSeas"; - String[] userTemplates = new String[] { - "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas", - "wrongname={0},ou=people,o=sevenSeas", - "cn={0},ou=people,o=sevenSeas", //this last one should work + String[] userTemplates = new String[]{ + "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas", + "wrongname={0},ou=people,o=sevenSeas", + "cn={0},ou=people,o=sevenSeas", //this last one should work }; RealmConfig config = new RealmConfig("ldap_realm", - buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), - globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), + globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService, threadPool); @@ -128,14 +127,14 @@ public void testBindWithTemplates() throws Exception { public void testBindWithBogusTemplates() throws Exception { String groupSearchBase = "o=sevenSeas"; - String[] userTemplates = new String[] { - "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas", - "wrongname={0},ou=people,o=sevenSeas", - "asdf={0},ou=people,o=sevenSeas", //none of these should work + String[] userTemplates = new String[]{ + "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas", + "wrongname={0},ou=people,o=sevenSeas", + "asdf={0},ou=people,o=sevenSeas", //none of these should work }; RealmConfig config = new RealmConfig("ldap_realm", - buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), - globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), + globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory ldapFac = new LdapSessionFactory(config, sslService, threadPool); @@ -153,8 +152,8 @@ public void testGroupLookupSubtree() throws Exception { String groupSearchBase = "o=sevenSeas"; String userTemplate = "cn={0},ou=people,o=sevenSeas"; RealmConfig config = new RealmConfig("ldap_realm", - buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), - globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), + globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory ldapFac = new LdapSessionFactory(config, sslService, threadPool); @@ -173,8 +172,8 @@ public void testGroupLookupOneLevel() throws Exception { String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas"; String userTemplate = "cn={0},ou=people,o=sevenSeas"; RealmConfig config = new RealmConfig("ldap_realm", - buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), - globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), + globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory ldapFac = new LdapSessionFactory(config, sslService, threadPool); @@ -192,8 +191,8 @@ public void testGroupLookupBase() throws Exception { String groupSearchBase = "cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"; String userTemplate = "cn={0},ou=people,o=sevenSeas"; RealmConfig config = new RealmConfig("ldap_realm", - buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), - globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); + buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), + globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)); LdapSessionFactory ldapFac = new LdapSessionFactory(config, sslService, threadPool); @@ -256,4 +255,31 @@ public void testSslTrustIsReloaded() throws Exception { session.close(); } + + public void testDeprecationWarningIfTls1IsUsed() throws Exception { + InMemoryDirectoryServer ldapServer = randomFrom(ldapServers); + String ldapUrl = new LDAPURL("ldaps", "localhost", ldapServer.getListenPort("ldaps-tls1"), null, null, null, null).toString(); + String groupSearchBase = "o=sevenSeas"; + String userTemplates = "cn={0},ou=people,o=sevenSeas"; + + Settings settings = Settings.builder() + .put(globalSettings) + .put(buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE)) + .build(); + + final Environment environment = TestEnvironment.newEnvironment(settings); + RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings, environment, new ThreadContext(settings)); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, sslService, threadPool); + String user = "Horatio Hornblower"; + SecureString userPass = new SecureString("pass"); + + LdapSession session = session(sessionFactory, user, userPass); + assertThat(session.userDn(), is("cn=Horatio Hornblower,ou=people,o=sevenSeas")); + session.close(); + + assertWarnings("a TLS v1.0 session was used for [ldap host localhost]," + + " this protocol will be disabled by default in a future version." + + " The [xpack.security.authc.realms.ldap_realm.ssl.supported_protocols] setting can be used to control this."); + + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java index 20eab4f453fd7..9958da51e7354 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapTestCase.java @@ -15,7 +15,7 @@ import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.SimpleBindRequest; - +import com.unboundid.util.ssl.SSLUtil; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; @@ -38,9 +38,7 @@ import org.junit.Before; import org.junit.BeforeClass; -import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509ExtendedKeyManager; @@ -75,18 +73,8 @@ public void startLdap() throws Exception { List listeners = new ArrayList<>(2); listeners.add(InMemoryListenerConfig.createLDAPConfig("ldap")); if (openLdapsPort()) { - final char[] ldapPassword = "ldap-password".toCharArray(); - final KeyStore ks = CertParsingUtils.getKeyStoreFromPEM( - getDataPath("/org/elasticsearch/xpack/security/authc/ldap/support/ldap-test-case.crt"), - getDataPath("/org/elasticsearch/xpack/security/authc/ldap/support/ldap-test-case.key"), - ldapPassword - ); - X509ExtendedKeyManager keyManager = CertParsingUtils.keyManager(ks, ldapPassword, KeyManagerFactory.getDefaultAlgorithm()); - final SSLContext context = SSLContext.getInstance("TLSv1.2"); - context.init(new KeyManager[] { keyManager }, null, null); - SSLServerSocketFactory serverSocketFactory = context.getServerSocketFactory(); - SSLSocketFactory clientSocketFactory = context.getSocketFactory(); - listeners.add(InMemoryListenerConfig.createLDAPSConfig("ldaps", null, 0, serverSocketFactory, clientSocketFactory)); + listeners.add(buildSslListener("ldaps", "TLSv1.2")); + listeners.add(buildSslListener("ldaps-tls1", "TLSv1")); } serverConfig.setListenerConfigs(listeners); InMemoryDirectoryServer ldapServer = new InMemoryDirectoryServer(serverConfig); @@ -103,6 +91,20 @@ public void startLdap() throws Exception { } } + private InMemoryListenerConfig buildSslListener(String listenerName, String sslProtocol) throws Exception { + final char[] ldapPassword = "ldap-password".toCharArray(); + final KeyStore ks = CertParsingUtils.getKeyStoreFromPEM( + getDataPath("/org/elasticsearch/xpack/security/authc/ldap/support/ldap-test-case.crt"), + getDataPath("/org/elasticsearch/xpack/security/authc/ldap/support/ldap-test-case.key"), + ldapPassword + ); + X509ExtendedKeyManager keyManager = CertParsingUtils.keyManager(ks, ldapPassword, KeyManagerFactory.getDefaultAlgorithm()); + final SSLUtil sslUtil = new SSLUtil(keyManager, null); + final SSLServerSocketFactory serverSocketFactory = sslUtil.createSSLServerSocketFactory(sslProtocol); + final SSLSocketFactory clientSocketFactory = sslUtil.createSSLSocketFactory(sslProtocol); + return InMemoryListenerConfig.createLDAPSConfig(listenerName, null, 0, serverSocketFactory, clientSocketFactory); + } + protected boolean openLdapsPort() { return false; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java index 6540be6a5eb14..52f245a0a4d9b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java @@ -23,6 +23,7 @@ import org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.VerificationMode; +import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.TlsDeprecationSocketVerifier; import org.junit.After; import org.junit.Before; @@ -57,7 +58,9 @@ public void testConnectionFactoryReturnsCorrectLDAPConnectionOptionsWithDefaultS assertThat(options.allowConcurrentSocketFactoryUse(), is(equalTo(true))); assertThat(options.getConnectTimeoutMillis(), is(equalTo(5000))); assertThat(options.getResponseTimeoutMillis(), is(equalTo(5000L))); - assertThat(options.getSSLSocketVerifier(), is(instanceOf(HostNameSSLSocketVerifier.class))); + assertThat(options.getSSLSocketVerifier(), is(instanceOf(TlsDeprecationSocketVerifier.class))); + final TlsDeprecationSocketVerifier sslSocketVerifier = (TlsDeprecationSocketVerifier) options.getSSLSocketVerifier(); + assertThat(sslSocketVerifier.getDelegate(), is(instanceOf(HostNameSSLSocketVerifier.class))); } public void testConnectionFactoryReturnsCorrectLDAPConnectionOptions() throws Exception { @@ -66,6 +69,7 @@ public void testConnectionFactoryReturnsCorrectLDAPConnectionOptions() throws Ex .put(SessionFactorySettings.HOSTNAME_VERIFICATION_SETTING, "false") .put(SessionFactorySettings.TIMEOUT_TCP_READ_SETTING, "20ms") .put(SessionFactorySettings.FOLLOW_REFERRALS_SETTING, "false") + .putList("ssl.supported_protocols", "TLSv1.2", "TLSv1.1") // disable TLS v1.0 so the verifier doesn't get wrapped .build(); final String realmName = "conn_settings"; @@ -88,7 +92,10 @@ public void testConnectionFactoryReturnsCorrectLDAPConnectionOptions() throws Ex assertWarnings("the setting [xpack.security.authc.realms." + realmName + ".hostname_verification] has been deprecated" + " and will be removed in a future version. use [xpack.security.authc.realms." + realmName + ".ssl.verification_mode] instead"); - settings = Settings.builder().put("ssl.verification_mode", VerificationMode.CERTIFICATE).build(); + settings = Settings.builder() + .put("ssl.verification_mode", VerificationMode.CERTIFICATE) + .putList("ssl.supported_protocols", "TLSv1.2", "TLSv1.1") // disable TLS v1.0 so the verifier doesn't get wrapped + .build(); realmConfig = new RealmConfig(realmName, settings, globalSettings.apply(settings), environment, threadContext); options = SessionFactory.connectionOptions(realmConfig, sslService.apply(settings), logger); assertThat(options.getSSLSocketVerifier(), is(instanceOf(TrustAllSSLSocketVerifier.class))); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java index 22b13f8330752..ba5e3c7c93542 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java @@ -66,6 +66,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -118,6 +119,7 @@ public void testReadIdpMetadataFromFile() throws Exception { } public void testReadIdpMetadataFromHttps() throws Exception { + final String serverProtocol = randomFrom("TLSv1", "TLSv1.1", "TLSv1.2"); final Path path = getDataPath("idp1.xml"); final String body = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); final MockSecureSettings mockSecureSettings = new MockSecureSettings(); @@ -134,7 +136,7 @@ public void testReadIdpMetadataFromHttps() throws Exception { .setSecureSettings(mockSecureSettings) .build(); TestsSSLService sslService = new TestsSSLService(settings, TestEnvironment.newEnvironment(settings)); - try (MockWebServer proxyServer = new MockWebServer(sslService.sslContext(Settings.EMPTY), false)) { + try (MockWebServer proxyServer = new MockWebServer(sslService.sslContext(Settings.EMPTY), false, singletonList(serverProtocol))) { proxyServer.start(); proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody(body).addHeader("Content-Type", "application/xml")); proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody(body).addHeader("Content-Type", "application/xml")); @@ -154,6 +156,12 @@ public void testReadIdpMetadataFromHttps() throws Exception { tuple.v1().destroy(); } } + + if (serverProtocol.equals("TLSv1")) { + assertWarnings("a TLS v1.0 session was used for [http connection to localhost]," + + " this protocol will be disabled by default in a future version." + + " The [xpack.security.authc.realms.my-saml.ssl.supported_protocols] setting can be used to control this."); + } } public void testAuthenticateWithRoleMapping() throws Exception { @@ -245,13 +253,13 @@ private AuthenticationResult performAuthentication(UserRoleMapper roleMapper, bo initializeRealms(realm, lookupRealm); - final SamlToken token = new SamlToken(new byte[0], Collections.singletonList("")); + final SamlToken token = new SamlToken(new byte[0], singletonList("")); final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.PERSISTENT, nameIdValue, idp.getEntityID(), sp.getEntityId(), null), randomAlphaOfLength(16), Arrays.asList( - new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.1", "uid", Collections.singletonList(uidValue)), + new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.1", "uid", singletonList(uidValue)), new SamlAttributes.SamlAttribute("urn:oid:1.3.6.1.4.1.5923.1.5.1.1", "groups", Arrays.asList("avengers", "shield")), new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", Arrays.asList("cbarton@shield.gov")) )); @@ -293,7 +301,7 @@ public void testAttributeSelectionWithRegex() throws Exception { final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(24), null, null, null), randomAlphaOfLength(16), - Collections.singletonList(new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", + singletonList(new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", Arrays.asList("john.smith@personal.example.net", "john.smith@corporate.example.com", "jsmith@corporate.example.com") ))); @@ -350,14 +358,14 @@ public void testNonMatchingPrincipalPatternThrowsSamlException() throws Exceptio final RealmConfig config = realmConfigFromRealmSettings(realmSettings); final SamlRealm realm = new SamlRealm(config, roleMapper, authenticator, logoutHandler, () -> idp, sp); - final SamlToken token = new SamlToken(new byte[0], Collections.singletonList("")); + final SamlToken token = new SamlToken(new byte[0], singletonList("")); for (String mail : Arrays.asList("john@your-corp.example.com", "john@mycorp.example.com.example.net", "john")) { final SamlAttributes attributes = new SamlAttributes( new SamlNameId(NameIDType.TRANSIENT, randomAlphaOfLength(12), null, null, null), randomAlphaOfLength(16), - Collections.singletonList( - new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", Collections.singletonList(mail)) + singletonList( + new SamlAttributes.SamlAttribute("urn:oid:0.9.2342.19200300.100.1.3", "mail", singletonList(mail)) )); when(authenticator.authenticate(token)).thenReturn(attributes); @@ -379,7 +387,7 @@ public void testCreateCredentialFromPemFiles() throws Exception { final PrivateKey encryptionKey = PemUtils.readPrivateKey(encryptionKeyPath, "encryption"::toCharArray); final Path encryptionCertPath = getDataPath("encryption.crt"); final Path destEncryptionCertPath = dir.resolve("encryption.crt"); - final X509Certificate encryptionCert = CertParsingUtils.readX509Certificates(Collections.singletonList(encryptionCertPath))[0]; + final X509Certificate encryptionCert = CertParsingUtils.readX509Certificates(singletonList(encryptionCertPath))[0]; Files.copy(encryptionKeyPath, destEncryptionKeyPath); Files.copy(encryptionCertPath, destEncryptionCertPath); builder.put(REALM_SETTINGS_PREFIX + ".encryption.key", destEncryptionKeyPath); @@ -583,8 +591,8 @@ public void testBuildLogoutRequest() throws Exception { final EntityDescriptor idp = mockIdp(); final IDPSSODescriptor role = mock(IDPSSODescriptor.class); final SingleLogoutService slo = SamlUtils.buildObject(SingleLogoutService.class, SingleLogoutService.DEFAULT_ELEMENT_NAME); - when(idp.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME)).thenReturn(Collections.singletonList(role)); - when(role.getSingleLogoutServices()).thenReturn(Collections.singletonList(slo)); + when(idp.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME)).thenReturn(singletonList(role)); + when(role.getSingleLogoutServices()).thenReturn(singletonList(slo)); slo.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); slo.setLocation("https://logout.saml/"); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/SecurityRestFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/SecurityRestFilterTests.java index d1ed07b42a9ca..3df9a01c85b15 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/SecurityRestFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/SecurityRestFilterTests.java @@ -28,6 +28,7 @@ import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.core.security.rest.RestRequestFilter; import org.elasticsearch.xpack.core.security.user.XPackUser; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -61,7 +62,7 @@ public void init() throws Exception { when(licenseState.isAuthAllowed()).thenReturn(true); restHandler = mock(RestHandler.class); filter = new SecurityRestFilter(licenseState, - new ThreadContext(Settings.EMPTY), authcService, restHandler, false); + new ThreadContext(Settings.EMPTY), authcService, restHandler, false, TLSv1DeprecationHandler.disabled()); } public void testProcess() throws Exception { @@ -138,7 +139,8 @@ public Set getFilteredFields() { callback.onResponse(new Authentication(XPackUser.INSTANCE, new RealmRef("test", "test", "t"), null)); return Void.TYPE; }).when(authcService).authenticate(any(RestRequest.class), any(ActionListener.class)); - filter = new SecurityRestFilter(licenseState, new ThreadContext(Settings.EMPTY), authcService, restHandler, false); + filter = new SecurityRestFilter(licenseState, new ThreadContext(Settings.EMPTY), authcService, restHandler, false, + TLSv1DeprecationHandler.disabled()); filter.handleRequest(restRequest, channel, null); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java index 6fda7d4c950bc..a7987fdbea397 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.transport.ssl; +import org.apache.http.Header; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; @@ -15,15 +16,21 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.client.transport.NoNodeAvailableException; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.test.MockLogAppender; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.Transport; import org.elasticsearch.xpack.core.TestXPackTransportClient; @@ -36,7 +43,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManagerFactory; - import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; @@ -53,11 +59,16 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; public class SslIntegrationTests extends SecurityIntegTestCase { @Override protected Settings nodeSettings(int nodeOrdinal) { - Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal)); + final Settings superSettings = super.nodeSettings(nodeOrdinal); + if (superSettings.keySet().stream().anyMatch(k -> k.contains("supported_protocols"))) { + throw new IllegalStateException("Node configured supported_protocols"); + } + Settings.Builder builder = Settings.builder().put(superSettings); addSSLSettingsForNodePEMFiles(builder, "xpack.security.http.", true); return builder.put(NetworkModule.HTTP_ENABLED.getKey(), true) .put("xpack.security.http.ssl.enabled", true) @@ -78,11 +89,11 @@ public void testThatUnconfiguredCiphersAreRejected() throws Exception { assumeFalse("the unconfigured ciphers list is empty", unconfiguredCiphers.isEmpty()); try (TransportClient transportClient = new TestXPackTransportClient(Settings.builder() - .put(transportClientSettings()) - .put("node.name", "programmatic_transport_client") - .put("cluster.name", internalCluster().getClusterName()) - .putList("xpack.ssl.cipher_suites", unconfiguredCiphers) - .build(), LocalStateSecurity.class)) { + .put(transportClientSettings()) + .put("node.name", "programmatic_transport_client") + .put("cluster.name", internalCluster().getClusterName()) + .putList("xpack.ssl.cipher_suites", unconfiguredCiphers) + .build(), LocalStateSecurity.class)) { TransportAddress transportAddress = randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses()); transportClient.addTransportAddress(transportAddress); @@ -94,14 +105,30 @@ public void testThatUnconfiguredCiphersAreRejected() throws Exception { } } + public void testThatTransportClientUsingDefaultSettingsIsAccepted() { + final String clusterName = internalCluster().getClusterName(); + try (TransportClient transportClient = new TestXPackTransportClient(Settings.builder() + .put(transportClientSettings()) + .put("node.name", "programmatic_transport_client") + .put("cluster.name", clusterName) + .build(), LocalStateSecurity.class)) { + + TransportAddress transportAddress = randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses()); + transportClient.addTransportAddress(transportAddress); + + final ClusterHealthResponse response = transportClient.admin().cluster().prepareHealth().get(); + assertThat(response.getClusterName(), is(clusterName)); + } + } + public void testThatTransportClientUsingSSLv3ProtocolIsRejected() { assumeFalse("Can't run in a FIPS JVM as SSLv3 SSLContext not available", inFipsJvm()); try (TransportClient transportClient = new TestXPackTransportClient(Settings.builder() - .put(transportClientSettings()) - .put("node.name", "programmatic_transport_client") - .put("cluster.name", internalCluster().getClusterName()) - .putList("xpack.security.transport.ssl.supported_protocols", new String[]{"SSLv3"}) - .build(), LocalStateSecurity.class)) { + .put(transportClientSettings()) + .put("node.name", "programmatic_transport_client") + .put("cluster.name", internalCluster().getClusterName()) + .putList("xpack.security.transport.ssl.supported_protocols", new String[]{"SSLv3"}) + .build(), LocalStateSecurity.class)) { TransportAddress transportAddress = randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses()); transportClient.addTransportAddress(transportAddress); @@ -122,14 +149,7 @@ public void testThatConnectionToHTTPWorks() throws Exception { Collections.singletonList("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt")); SSLService service = new SSLService(builder.build(), null); - CredentialsProvider provider = new BasicCredentialsProvider(); - provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(nodeClientUsername(), - new String(nodeClientPassword().getChars()))); - SSLConfiguration sslConfiguration = service.getSSLConfiguration("xpack.security.transport.ssl"); - try (CloseableHttpClient client = HttpClients.custom() - .setSSLSocketFactory(new SSLConnectionSocketFactory(service.sslSocketFactory(sslConfiguration), - SSLConnectionSocketFactory.getDefaultHostnameVerifier())) - .setDefaultCredentialsProvider(provider).build(); + try (CloseableHttpClient client = buildHttpClient(service); CloseableHttpResponse response = SocketAccess.doPrivileged(() -> client.execute(new HttpGet(getNodeUrl())))) { assertThat(response.getStatusLine().getStatusCode(), is(200)); String data = Streams.copyToString(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)); @@ -144,8 +164,8 @@ public void testThatHttpUsingSSLv3IsRejected() throws Exception { factory.init((KeyStore) null); sslContext.init(null, factory.getTrustManagers(), new SecureRandom()); - SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, new String[]{ "SSLv3" }, null, - NoopHostnameVerifier.INSTANCE); + SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv3"}, null, + NoopHostnameVerifier.INSTANCE); try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sf).build()) { CloseableHttpResponse result = SocketAccess.doPrivileged(() -> client.execute(new HttpGet(getNodeUrl()))); fail("Expected a connection error due to SSLv3 not being supported by default"); @@ -154,10 +174,101 @@ public void testThatHttpUsingSSLv3IsRejected() throws Exception { } } + public void testServerLogsDeprecationWarningWhenTransportClientConnectsWithTLS1() throws Exception { + assumeFalse("Can't run in a FIPS JVM with verification mode 'none'", inFipsJvm()); + Logger logger = + LogManager.getLogger("org.elasticsearch.deprecation.xpack.core.security.transport.netty4.SecurityNetty4Transport"); + MockLogAppender appender = expectTls1DeprecationWarnings(logger, "xpack.security.transport.ssl", "transport profile: default"); + try { + final String clusterName = internalCluster().getClusterName(); + try (TransportClient transportClient = new TestXPackTransportClient(Settings.builder() + .put(transportClientSettings()) + .put("node.name", "programmatic_transport_client") + .put("cluster.name", clusterName) + .putList("xpack.security.transport.ssl.supported_protocols", "TLSv1") + .put("xpack.security.transport.ssl.verification_mode", "none") + // For some unknown reason, verification mode 'none' is needed with TLSv1, but only for some test seeds. + .build(), LocalStateSecurity.class)) { + + final Transport transport = internalCluster().getInstance(Transport.class); + TransportAddress transportAddress = randomFrom(transport.boundAddress().boundAddresses()); + transportClient.addTransportAddress(transportAddress); + + final ClusterHealthResponse response = transportClient.admin().cluster().prepareHealth().get(); + assertThat(response.getClusterName(), is(clusterName)); + } + appender.assertAllExpectationsMatched(); + } finally { + Loggers.removeAppender(logger, appender); + appender.stop(); + } + } + + public void testDeprecationWarningWhenHttpClientConnectsWithTLS1() throws Exception { + Logger logger = + LogManager.getLogger("org.elasticsearch.deprecation.xpack.security.Security"); + MockLogAppender appender = expectTls1DeprecationWarnings(logger, "xpack.security.http.ssl", "HTTP connection from *"); + + try { + Settings.Builder builder = Settings.builder().putList("xpack.security.transport.ssl.supported_protocols", "TLSv1"); + addSSLSettingsForPEMFiles( + builder, "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.pem", + "testclient", + "/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt", + Collections.singletonList("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt")); + SSLService service = new SSLService(builder.build(), null); + + try (CloseableHttpClient client = buildHttpClient(service); + CloseableHttpResponse response = SocketAccess.doPrivileged(() -> client.execute(new HttpGet(getNodeUrl())))) { + assertThat(response.getStatusLine().getStatusCode(), is(200)); + final Header warningHeader = response.getFirstHeader("Warning"); + assertThat(warningHeader, notNullValue()); + assertThat(warningHeader.getValue(), containsString("a TLS v1.0 session was used for [HTTP connection")); + assertThat(warningHeader.getValue(), + containsString("The [xpack.security.http.ssl.supported_protocols] setting can be used to control this.")); + } + appender.assertAllExpectationsMatched(); + } finally { + Loggers.removeAppender(logger, appender); + appender.stop(); + } + } + + private CloseableHttpClient buildHttpClient(SSLService service) { + CredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(nodeClientUsername(), + new String(nodeClientPassword().getChars()))); + SSLConfiguration sslConfiguration = service.getSSLConfiguration("xpack.security.transport.ssl"); + final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + service.sslSocketFactory(sslConfiguration), + SSLConnectionSocketFactory.getDefaultHostnameVerifier()); + + return HttpClients.custom() + .setSSLSocketFactory(socketFactory) + .setDefaultCredentialsProvider(provider) + .build(); + } + private String getNodeUrl() { TransportAddress transportAddress = - randomFrom(internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddresses()); + randomFrom(internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddresses()); final InetSocketAddress inetSocketAddress = transportAddress.address(); return String.format(Locale.ROOT, "https://%s/", NetworkAddress.format(inetSocketAddress)); } + + private MockLogAppender expectTls1DeprecationWarnings(Logger logger, String settingPrefix, String description) + throws IllegalAccessException { + + MockLogAppender appender = new MockLogAppender(); + Loggers.addAppender(logger, appender); + appender.addExpectation(new MockLogAppender.SeenEventExpectation("TLS v1.0 deprecation warning", + logger.getName(), Level.WARN, + "a TLS v1.0 session was used for [" + description + "]," + + " this protocol will be disabled by default in a future version." + + " The [" + settingPrefix + ".supported_protocols] setting can be used to control this." + )); + appender.start(); + return appender; + } + } diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/http/HttpClient.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/http/HttpClient.java index 2a327851558e5..bf1e8d5bd37c2 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/http/HttpClient.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/http/HttpClient.java @@ -44,6 +44,7 @@ import org.elasticsearch.xpack.core.common.socket.SocketAccess; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler; import org.elasticsearch.xpack.core.watcher.crypto.CryptoService; import javax.net.ssl.HostnameVerifier; @@ -84,9 +85,11 @@ public HttpClient(Settings settings, SSLService sslService, CryptoService crypto HttpClientBuilder clientBuilder = HttpClientBuilder.create(); // ssl setup + TLSv1DeprecationHandler tlsDeprecationHandler = new TLSv1DeprecationHandler(SETTINGS_SSL_PREFIX, settings, logger); SSLConfiguration sslConfiguration = sslService.getSSLConfiguration(SETTINGS_SSL_PREFIX); boolean isHostnameVerificationEnabled = sslConfiguration.verificationMode().isHostnameVerificationEnabled(); HostnameVerifier verifier = isHostnameVerificationEnabled ? new DefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE; + verifier = sslService.wrapHostnameVerifier(verifier, tlsDeprecationHandler); SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslService.sslSocketFactory(sslConfiguration), verifier); clientBuilder.setSSLSocketFactory(factory); diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/http/HttpClientTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/http/HttpClientTests.java index bb66b7a6a9fb8..b820d31abd93b 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/http/HttpClientTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/http/HttpClientTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.test.http.MockResponse; import org.elasticsearch.test.http.MockWebServer; import org.elasticsearch.test.junit.annotations.Network; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.TestsSSLService; import org.elasticsearch.xpack.core.ssl.VerificationMode; @@ -40,6 +41,8 @@ import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -201,6 +204,35 @@ public void testHttps() throws Exception { } } + public void testHttpsWithTLSv1GeneratesWarnings() throws Exception { + Path trustedCertPath = getDataPath("/org/elasticsearch/xpack/security/keystore/truststore-testnode-only.crt"); + Path certPath = getDataPath("/org/elasticsearch/xpack/security/keystore/testnode.crt"); + Path keyPath = getDataPath("/org/elasticsearch/xpack/security/keystore/testnode.pem"); + MockSecureSettings secureSettings = new MockSecureSettings(); + Settings settings = Settings.builder() + .put("xpack.http.ssl.certificate_authorities", trustedCertPath) + .setSecureSettings(secureSettings) + .build(); + + try (HttpClient client = new HttpClient(settings, new SSLService(settings, environment), null)) { + secureSettings = new MockSecureSettings(); + secureSettings.setString("xpack.security.http.ssl.secure_key_passphrase", "testnode"); + Settings settings2 = Settings.builder() + .put("xpack.security.http.ssl.key", keyPath) + .put("xpack.security.http.ssl.certificate", certPath) + .put("xpack.watcher.enabled", false) + .setSecureSettings(secureSettings) + .build(); + + TestsSSLService sslService = new TestsSSLService(settings2, environment); + testSslMockWebserver(client, sslService.sslContext("xpack.security.http.ssl."), false, Collections.singletonList("TLSv1")); + } + + assertWarnings("a TLS v1.0 session was used for [http connection to localhost]," + + " this protocol will be disabled by default in a future version." + + " The [xpack.http.ssl.supported_protocols] setting can be used to control this."); + } + public void testHttpsDisableHostnameVerification() throws Exception { Path certPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-no-subjaltname.crt"); Path keyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode-no-subjaltname.pem"); @@ -269,7 +301,12 @@ public void testHttpsClientAuth() throws Exception { } private void testSslMockWebserver(HttpClient client, SSLContext sslContext, boolean needClientAuth) throws IOException { - try (MockWebServer mockWebServer = new MockWebServer(sslContext, needClientAuth)) { + testSslMockWebserver(client, sslContext, needClientAuth, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS); + } + + private void testSslMockWebserver(HttpClient client, SSLContext sslContext, boolean needClientAuth, List supportedProtocols) + throws IOException { + try (MockWebServer mockWebServer = new MockWebServer(sslContext, needClientAuth, supportedProtocols)) { mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("body")); mockWebServer.start();