Skip to content

Commit 1f41c7c

Browse files
authored
Issue deprecation warning if TLSv1.0 is used without explicit config (#37788)
TLSv1.0 will be removed from the default list of supported protocols in v7.0. This change adds deprecation warnings when a TLS v1.0 connection is used without having been explictly configured as a supported protocol. Such situations will fail in Elasticsearch 7.x This covers: - Incoming connections on transport or https. - Outgoing http connections for watcher, monitoring & saml metadata - Outgoing http connections for ldap & AD. Deprecations for incoming HTTP connections are included in the Warning headers sent back to that client. For the other contexts, the deprecation log must be used.
1 parent 9bcb0b5 commit 1f41c7c

File tree

24 files changed

+842
-112
lines changed

24 files changed

+842
-112
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.apache.logging.log4j.message.ParameterizedMessage;
1717
import org.elasticsearch.Version;
1818
import org.elasticsearch.cluster.node.DiscoveryNode;
19+
import org.elasticsearch.common.Nullable;
1920
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
2021
import org.elasticsearch.common.network.CloseableChannel;
2122
import org.elasticsearch.common.network.NetworkService;
@@ -31,17 +32,20 @@
3132
import org.elasticsearch.xpack.core.security.transport.SSLExceptionHelper;
3233
import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
3334
import org.elasticsearch.xpack.core.ssl.SSLService;
35+
import org.elasticsearch.xpack.core.ssl.TLSv1DeprecationHandler;
3436

3537
import javax.net.ssl.SNIHostName;
3638
import javax.net.ssl.SNIServerName;
3739
import javax.net.ssl.SSLEngine;
3840
import javax.net.ssl.SSLParameters;
41+
import javax.net.ssl.SSLSession;
3942
import java.net.InetSocketAddress;
4043
import java.net.SocketAddress;
4144
import java.util.Collections;
4245
import java.util.HashMap;
4346
import java.util.Map;
4447
import java.util.Set;
48+
import java.util.function.Consumer;
4549

4650
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
4751

@@ -55,6 +59,7 @@ public class SecurityNetty4Transport extends Netty4Transport {
5559
private final SSLConfiguration sslConfiguration;
5660
private final Map<String, SSLConfiguration> profileConfiguration;
5761
private final boolean sslEnabled;
62+
private final Map<String, TLSv1DeprecationHandler> tlsDeprecation;
5863

5964
public SecurityNetty4Transport(
6065
final Settings settings,
@@ -72,18 +77,42 @@ public SecurityNetty4Transport(
7277
this.sslConfiguration = sslService.getSSLConfiguration(setting("transport.ssl."));
7378
Map<String, SSLConfiguration> profileConfiguration = getTransportProfileConfigurations(settings, sslService, sslConfiguration);
7479
this.profileConfiguration = Collections.unmodifiableMap(profileConfiguration);
80+
this.tlsDeprecation = buildTlsDeprecationHandlers(settings, profileConfiguration.keySet());
7581
} else {
7682
this.profileConfiguration = Collections.emptyMap();
7783
this.sslConfiguration = null;
84+
this.tlsDeprecation = Collections.emptyMap();
7885
}
7986
}
8087

88+
// Package protected for testing
89+
static Map<String, TLSv1DeprecationHandler> buildTlsDeprecationHandlers(Settings settings, Set<String> profileNames) {
90+
TLSv1DeprecationHandler defaultTlsDeprecationHandler = new TLSv1DeprecationHandler(setting("transport.ssl."), settings, logger);
91+
if (defaultTlsDeprecationHandler.shouldLogWarnings()) {
92+
final Map<String, TLSv1DeprecationHandler> handlers = new HashMap<>();
93+
profileNames.forEach(name -> {
94+
if (TransportSettings.DEFAULT_PROFILE.equals(name)) {
95+
handlers.put(name, defaultTlsDeprecationHandler);
96+
} else {
97+
handlers.put(name, new TLSv1DeprecationHandler(getTransportProfileSslPrefix(name) + ".", settings, logger));
98+
}
99+
});
100+
return Collections.unmodifiableMap(handlers);
101+
} else {
102+
return Collections.emptyMap();
103+
}
104+
}
105+
106+
private static String getTransportProfileSslPrefix(String name) {
107+
return "transport.profiles." + name + "." + setting("ssl") ;
108+
}
109+
81110
public static Map<String, SSLConfiguration> getTransportProfileConfigurations(Settings settings, SSLService sslService,
82111
SSLConfiguration defaultConfiguration) {
83112
Set<String> profileNames = settings.getGroups("transport.profiles.", true).keySet();
84113
Map<String, SSLConfiguration> profileConfiguration = new HashMap<>(profileNames.size() + 1);
85114
for (String profileName : profileNames) {
86-
SSLConfiguration configuration = sslService.getSSLConfiguration("transport.profiles." + profileName + "." + setting("ssl"));
115+
SSLConfiguration configuration = sslService.getSSLConfiguration(getTransportProfileSslPrefix(profileName));
87116
profileConfiguration.put(profileName, configuration);
88117
}
89118

@@ -155,9 +184,13 @@ public void onException(TcpChannel channel, Exception e) {
155184
public class SslChannelInitializer extends ServerChannelInitializer {
156185
private final SSLConfiguration configuration;
157186

158-
public SslChannelInitializer(String name, SSLConfiguration configuration) {
187+
@Nullable
188+
private final Consumer<SSLSession> handshakeListener;
189+
190+
public SslChannelInitializer(String name, SSLConfiguration configuration, Consumer<SSLSession> handshakeListener) {
159191
super(name);
160192
this.configuration = configuration;
193+
this.handshakeListener = handshakeListener;
161194
}
162195

163196
@Override
@@ -167,11 +200,30 @@ protected void initChannel(Channel ch) throws Exception {
167200
serverEngine.setUseClientMode(false);
168201
final SslHandler sslHandler = new SslHandler(serverEngine);
169202
ch.pipeline().addFirst("sslhandler", sslHandler);
203+
if (handshakeListener != null) {
204+
sslHandler.handshakeFuture().addListener(fut -> {
205+
SSLSession session = serverEngine.getSession();
206+
handshakeListener.accept(session);
207+
});
208+
}
170209
}
171210
}
172211

173212
protected ServerChannelInitializer getSslChannelInitializer(final String name, final SSLConfiguration configuration) {
174-
return new SslChannelInitializer(name, sslConfiguration);
213+
return new SslChannelInitializer(name, sslConfiguration, getSslHandshakeListenerForProfile(name));
214+
}
215+
216+
@Nullable
217+
protected Consumer<SSLSession> getSslHandshakeListenerForProfile(String name) {
218+
// This is handled here (rather than as an interceptor) so we know which profile was used, and can
219+
// provide the correct setting to report on (this may be technically also possible in a transport interceptor, but the
220+
// existing security interceptor doesn't have that now, and adding it would be a more intrusive change).
221+
final TLSv1DeprecationHandler deprecationHandler = tlsDeprecation.get(name);
222+
if (deprecationHandler != null && deprecationHandler.shouldLogWarnings()) {
223+
return session -> deprecationHandler.checkAndLog(session, () -> "transport profile: " + name);
224+
} else {
225+
return null;
226+
}
175227
}
176228

177229
private class SecurityClientChannelInitializer extends ClientChannelInitializer {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.elasticsearch.xpack.core.XPackSettings;
1515

1616
import javax.net.ssl.TrustManagerFactory;
17-
1817
import java.security.KeyStore;
1918
import java.util.Arrays;
2019
import java.util.Collection;
@@ -62,7 +61,7 @@ public class SSLConfigurationSettings {
6261
public static final Setting<List<String>> CIPHERS_SETTING_PROFILES = Setting.affixKeySetting("transport.profiles.",
6362
"xpack.security.ssl.cipher_suites", CIPHERS_SETTING_TEMPLATE);
6463

65-
private static final Function<String,Setting<List<String>>> SUPPORTED_PROTOCOLS_TEMPLATE = key -> Setting.listSetting(key,
64+
static final Function<String, Setting<List<String>>> SUPPORTED_PROTOCOLS_TEMPLATE = key -> Setting.listSetting(key,
6665
Collections.emptyList(), Function.identity(), propertiesFromKey(key));
6766
public static final Setting<List<String>> SUPPORTED_PROTOCOLS_PROFILES = Setting.affixKeySetting("transport.profiles.",
6867
"xpack.security.ssl.supported_protocols", SUPPORTED_PROTOCOLS_TEMPLATE) ;

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,20 @@ SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) {
155155
*
156156
* @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return
157157
* a context created from the default configuration
158+
* @param tlsDeprecationHandler a handler in case TLSv1.0 is used for an SSL connection under this strategy
158159
* @return Never {@code null}.
159160
* @deprecated This method will fail if the SSL configuration uses a {@link org.elasticsearch.common.settings.SecureSetting} but the
160161
* {@link org.elasticsearch.common.settings.SecureSettings} have been closed. Use {@link #getSSLConfiguration(String)}
161-
* and {@link #sslIOSessionStrategy(SSLConfiguration)} (Deprecated, but not removed because monitoring uses dynamic SSL settings)
162+
* and {@link #sslIOSessionStrategy(SSLConfiguration, TLSv1DeprecationHandler)}
163+
* (Deprecated, but not removed because monitoring uses dynamic SSL settings)
162164
*/
163165
@Deprecated
164-
public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings) {
166+
public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings, TLSv1DeprecationHandler tlsDeprecationHandler) {
165167
SSLConfiguration config = sslConfiguration(settings);
166-
return sslIOSessionStrategy(config);
168+
return sslIOSessionStrategy(config, tlsDeprecationHandler);
167169
}
168170

169-
public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config) {
171+
public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config, TLSv1DeprecationHandler tlsDeprecationHandler) {
170172
SSLContext sslContext = sslContext(config);
171173
String[] ciphers = supportedCiphers(sslParameters(sslContext).getCipherSuites(), config.cipherSuites(), false);
172174
String[] supportedProtocols = config.supportedProtocols().toArray(Strings.EMPTY_ARRAY);
@@ -177,10 +179,24 @@ public SSLIOSessionStrategy sslIOSessionStrategy(SSLConfiguration config) {
177179
} else {
178180
verifier = NoopHostnameVerifier.INSTANCE;
179181
}
182+
verifier = wrapHostnameVerifier(verifier, tlsDeprecationHandler);
180183

181184
return sslIOSessionStrategy(sslContext, supportedProtocols, ciphers, verifier);
182185
}
183186

187+
public HostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier, TLSv1DeprecationHandler tlsDeprecationHandler) {
188+
// Using the hostname verifier for this is just ugly, but HTTP client doesn't expose the SSLSession in many places
189+
// and this is the easiest one to hook into in a non-intrusive way
190+
if (tlsDeprecationHandler.shouldLogWarnings()) {
191+
return (hostname, session) -> {
192+
tlsDeprecationHandler.checkAndLog(session, () -> "http connection to " + hostname);
193+
return verifier.verify(hostname, session);
194+
};
195+
} else {
196+
return verifier;
197+
}
198+
}
199+
184200
/**
185201
* The {@link SSLParameters} that are associated with the {@code sslContext}.
186202
* <p>
@@ -194,8 +210,8 @@ SSLParameters sslParameters(SSLContext sslContext) {
194210
}
195211

196212
/**
197-
* This method only exists to simplify testing of {@link #sslIOSessionStrategy(Settings)} because {@link SSLIOSessionStrategy} does
198-
* not expose any of the parameters that you give it.
213+
* This method only exists to simplify testing of {@link #sslIOSessionStrategy(Settings, TLSv1DeprecationHandler)} because
214+
* {@link SSLIOSessionStrategy} does not expose any of the parameters that you give it.
199215
*
200216
* @param sslContext SSL Context used to handle SSL / TCP requests
201217
* @param protocols Supported protocols
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.ssl;
8+
9+
import org.apache.logging.log4j.Logger;
10+
import org.elasticsearch.common.logging.DeprecationLogger;
11+
import org.elasticsearch.common.settings.Settings;
12+
13+
import javax.net.ssl.SSLSession;
14+
import java.time.LocalDate;
15+
import java.time.ZoneId;
16+
import java.util.function.Supplier;
17+
18+
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.SUPPORTED_PROTOCOLS_TEMPLATE;
19+
20+
/**
21+
* Handles logging deprecation warnings when a TLSv1.0 SSL connection is used, and that SSL context relies on
22+
* the default list of supported_protocols (in Elasticsearch 7.0, this list will not include TLS 1.0).
23+
*/
24+
public class TLSv1DeprecationHandler {
25+
26+
private final String supportedProtocolsSetting;
27+
private final boolean shouldLogWarnings;
28+
private final DeprecationLogger deprecationLogger;
29+
30+
public TLSv1DeprecationHandler(String settingPrefix, Settings settings, Logger baseLogger) {
31+
if (settingPrefix.length() > 0 && settingPrefix.endsWith("ssl.") == false) {
32+
throw new IllegalArgumentException("Setting prefix [" + settingPrefix + "] must end in 'ssl.'");
33+
}
34+
this.supportedProtocolsSetting = settingPrefix + "supported_protocols";
35+
this.shouldLogWarnings = SUPPORTED_PROTOCOLS_TEMPLATE.apply(supportedProtocolsSetting).exists(settings) == false;
36+
if (shouldLogWarnings) {
37+
deprecationLogger = new DeprecationLogger(baseLogger);
38+
} else {
39+
deprecationLogger = null;
40+
}
41+
}
42+
43+
private TLSv1DeprecationHandler(String settingKey, boolean shouldLog, DeprecationLogger logger) {
44+
this.supportedProtocolsSetting = settingKey;
45+
this.shouldLogWarnings = shouldLog;
46+
this.deprecationLogger = logger;
47+
}
48+
49+
public static TLSv1DeprecationHandler disabled() {
50+
return new TLSv1DeprecationHandler(null, false, null);
51+
}
52+
53+
public boolean shouldLogWarnings() {
54+
return shouldLogWarnings;
55+
}
56+
57+
public void checkAndLog(SSLSession session, Supplier<String> descriptionSupplier) {
58+
if (shouldLogWarnings == false) {
59+
return;
60+
}
61+
if ("TLSv1".equals(session.getProtocol())) {
62+
final String description = descriptionSupplier.get();
63+
// Use a "LRU" key that is unique per day. That way each description (source address, etc) will be logged once per day.
64+
final String key = LocalDate.now(ZoneId.of("UTC")) + ":" + description;
65+
deprecationLogger.deprecatedAndMaybeLog(key,
66+
"a TLS v1.0 session was used for [{}], " +
67+
"this protocol will be disabled by default in a future version. " +
68+
"The [{}] setting can be used to control this.",
69+
description, supportedProtocolsSetting);
70+
}
71+
}
72+
}

x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
import com.sun.net.httpserver.HttpsConfigurator;
1111
import com.sun.net.httpserver.HttpsParameters;
1212
import com.sun.net.httpserver.HttpsServer;
13-
import org.apache.logging.log4j.Logger;
1413
import org.apache.logging.log4j.LogManager;
14+
import org.apache.logging.log4j.Logger;
1515
import org.apache.logging.log4j.message.ParameterizedMessage;
1616
import org.apache.logging.log4j.util.Supplier;
1717
import org.elasticsearch.common.Strings;
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.common.unit.TimeValue;
2121
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
2222
import org.elasticsearch.mocksocket.MockHttpServer;
23+
import org.elasticsearch.xpack.core.XPackSettings;
2324

2425
import javax.net.ssl.SSLContext;
2526
import java.io.Closeable;
@@ -55,6 +56,7 @@ public class MockWebServer implements Closeable {
5556
private final Logger logger;
5657
private final SSLContext sslContext;
5758
private final boolean needClientAuth;
59+
private final List<String> supportedProtocols;
5860
private final Set<CountDownLatch> latches = ConcurrentCollections.newConcurrentSet();
5961
private String hostname;
6062
private int port;
@@ -72,9 +74,20 @@ public MockWebServer() {
7274
* @param needClientAuth Should clientAuth be used, which requires a client side certificate
7375
*/
7476
public MockWebServer(SSLContext sslContext, boolean needClientAuth) {
77+
this(sslContext, needClientAuth, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS);
78+
}
79+
80+
/**
81+
* Instantiates a webserver with https
82+
* @param sslContext The SSL context to be used for encryption
83+
* @param needClientAuth Should clientAuth be used, which requires a client side certificate
84+
* @param supportedProtocols Which SSL/TLS protocols version should be supported
85+
*/
86+
public MockWebServer(SSLContext sslContext, boolean needClientAuth, List<String> supportedProtocols) {
7587
this.needClientAuth = needClientAuth;
7688
this.logger = LogManager.getLogger(this.getClass());
7789
this.sslContext = sslContext;
90+
this.supportedProtocols = supportedProtocols;
7891
}
7992

8093
/**
@@ -87,7 +100,7 @@ public void start() throws IOException {
87100
InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0);
88101
if (sslContext != null) {
89102
HttpsServer httpsServer = MockHttpServer.createHttps(address, 0);
90-
httpsServer.setHttpsConfigurator(new CustomHttpsConfigurator(sslContext, needClientAuth));
103+
httpsServer.setHttpsConfigurator(new CustomHttpsConfigurator(sslContext, needClientAuth, supportedProtocols));
91104
server = httpsServer;
92105
} else {
93106
server = MockHttpServer.createHttp(address, 0);
@@ -144,15 +157,18 @@ public void start() throws IOException {
144157
private static final class CustomHttpsConfigurator extends HttpsConfigurator {
145158

146159
private final boolean needClientAuth;
160+
private final List<String> supportedProtocols;
147161

148-
CustomHttpsConfigurator(SSLContext sslContext, boolean needClientAuth) {
162+
CustomHttpsConfigurator(SSLContext sslContext, boolean needClientAuth, List<String> supportedProtocols) {
149163
super(sslContext);
150164
this.needClientAuth = needClientAuth;
165+
this.supportedProtocols = supportedProtocols;
151166
}
152167

153168
@Override
154169
public void configure(HttpsParameters params) {
155170
params.setNeedClientAuth(needClientAuth);
171+
params.setProtocols(this.supportedProtocols.toArray(Strings.EMPTY_ARRAY));
156172
}
157173
}
158174

0 commit comments

Comments
 (0)