Skip to content

Commit 3cae269

Browse files
authored
Remove TLSv1.2 pinning in ssl reload tests (elastic#38651)
This change removes the pinning of TLSv1.2 in the SSLConfigurationReloaderTests that had been added to workaround an issue with the MockWebServer and Apache HttpClient when using TLSv1.3. The way HttpClient closes the socket causes issues with the TLSv1.3 SSLEngine implementation that causes the MockWebServer to loop endlessly trying to send the close message back to the client. This change wraps the created http connection in a way that allows us to override the closing behavior of HttpClient. An upstream request with HttpClient has been opened at https://issues.apache.org/jira/browse/HTTPCORE-571 to see if the method of closing can be special cased for SSLSocket instances. This is caused by a JDK bug, JDK-8214418 which is fixed by https://hg.openjdk.java.net/jdk/jdk12/rev/5022a4915fe9. Relates elastic#38646
1 parent eac14af commit 3cae269

File tree

1 file changed

+156
-18
lines changed

1 file changed

+156
-18
lines changed

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,24 @@
55
*/
66
package org.elasticsearch.xpack.core.ssl;
77

8+
import org.apache.http.HttpConnectionMetrics;
9+
import org.apache.http.HttpEntityEnclosingRequest;
10+
import org.apache.http.HttpException;
11+
import org.apache.http.HttpRequest;
12+
import org.apache.http.HttpResponse;
813
import org.apache.http.client.methods.HttpGet;
14+
import org.apache.http.config.RegistryBuilder;
15+
import org.apache.http.conn.HttpConnectionFactory;
16+
import org.apache.http.conn.ManagedHttpClientConnection;
17+
import org.apache.http.conn.routing.HttpRoute;
18+
import org.apache.http.conn.socket.ConnectionSocketFactory;
19+
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
20+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
21+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
922
import org.apache.http.impl.client.CloseableHttpClient;
1023
import org.apache.http.impl.client.HttpClients;
24+
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
25+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
1126
import org.apache.http.ssl.SSLContextBuilder;
1227
import org.elasticsearch.common.CheckedRunnable;
1328
import org.elasticsearch.common.settings.MockSecureSettings;
@@ -26,9 +41,13 @@
2641

2742
import javax.net.ssl.SSLContext;
2843
import javax.net.ssl.SSLHandshakeException;
44+
import javax.net.ssl.SSLSession;
45+
import javax.net.ssl.SSLSocket;
2946
import java.io.IOException;
3047
import java.io.InputStream;
3148
import java.io.OutputStream;
49+
import java.net.InetAddress;
50+
import java.net.Socket;
3251
import java.nio.file.AtomicMoveNotSupportedException;
3352
import java.nio.file.Files;
3453
import java.nio.file.Path;
@@ -47,6 +66,7 @@
4766
import java.util.Collections;
4867
import java.util.List;
4968
import java.util.concurrent.CountDownLatch;
69+
import java.util.concurrent.TimeUnit;
5070
import java.util.function.Consumer;
5171

5272
import static org.hamcrest.Matchers.containsString;
@@ -91,7 +111,6 @@ public void testReloadingKeyStore() throws Exception {
91111
final Settings settings = Settings.builder()
92112
.put("path.home", createTempDir())
93113
.put("xpack.security.transport.ssl.keystore.path", keystorePath)
94-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
95114
.setSecureSettings(secureSettings)
96115
.build();
97116
final Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
@@ -150,7 +169,6 @@ public void testPEMKeyConfigReloading() throws Exception {
150169
.put("xpack.security.transport.ssl.key", keyPath)
151170
.put("xpack.security.transport.ssl.certificate", certPath)
152171
.putList("xpack.security.transport.ssl.certificate_authorities", certPath.toString())
153-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
154172
.setSecureSettings(secureSettings)
155173
.build();
156174
final Environment env = randomBoolean() ? null :
@@ -207,15 +225,14 @@ public void testReloadingTrustStore() throws Exception {
207225
secureSettings.setString("xpack.security.transport.ssl.truststore.secure_password", "testnode");
208226
Settings settings = Settings.builder()
209227
.put("xpack.security.transport.ssl.truststore.path", trustStorePath)
210-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
211228
.put("path.home", createTempDir())
212229
.setSecureSettings(secureSettings)
213230
.build();
214231
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
215232
// Create the MockWebServer once for both pre and post checks
216233
try (MockWebServer server = getSslServer(trustStorePath, "testnode")) {
217234
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
218-
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()) {
235+
try (CloseableHttpClient client = createHttpClient(context)) {
219236
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
220237
} catch (Exception e) {
221238
throw new RuntimeException("Error connecting to the mock server", e);
@@ -232,7 +249,7 @@ public void testReloadingTrustStore() throws Exception {
232249

233250
// Client's truststore doesn't contain the server's certificate anymore so SSLHandshake should fail
234251
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
235-
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()) {
252+
try (CloseableHttpClient client = createHttpClient(updatedContext)) {
236253
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
237254
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
238255
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
@@ -259,14 +276,13 @@ public void testReloadingPEMTrustConfig() throws Exception {
259276
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode_updated.crt"), updatedCert);
260277
Settings settings = Settings.builder()
261278
.putList("xpack.security.transport.ssl.certificate_authorities", serverCertPath.toString())
262-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
263279
.put("path.home", createTempDir())
264280
.build();
265281
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
266282
// Create the MockWebServer once for both pre and post checks
267283
try (MockWebServer server = getSslServer(serverKeyPath, serverCertPath, "testnode")) {
268284
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
269-
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()) {
285+
try (CloseableHttpClient client = createHttpClient(context)) {
270286
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())));//.close());
271287
} catch (Exception e) {
272288
throw new RuntimeException("Exception connecting to the mock server", e);
@@ -283,7 +299,7 @@ public void testReloadingPEMTrustConfig() throws Exception {
283299

284300
// Client doesn't trust the Server certificate anymore so SSLHandshake should fail
285301
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
286-
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()) {
302+
try (CloseableHttpClient client = createHttpClient(updatedContext)) {
287303
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
288304
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
289305
assertThat(sslException.getCause().getMessage(), containsString("PKIX path validation failed"));
@@ -308,7 +324,6 @@ public void testReloadingKeyStoreException() throws Exception {
308324
secureSettings.setString("xpack.security.transport.ssl.keystore.secure_password", "testnode");
309325
Settings settings = Settings.builder()
310326
.put("xpack.security.transport.ssl.keystore.path", keystorePath)
311-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
312327
.setSecureSettings(secureSettings)
313328
.put("path.home", createTempDir())
314329
.build();
@@ -350,7 +365,6 @@ public void testReloadingPEMKeyConfigException() throws Exception {
350365
.put("xpack.security.transport.ssl.key", keyPath)
351366
.put("xpack.security.transport.ssl.certificate", certPath)
352367
.putList("xpack.security.transport.ssl.certificate_authorities", certPath.toString(), clientCertPath.toString())
353-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
354368
.put("path.home", createTempDir())
355369
.setSecureSettings(secureSettings)
356370
.build();
@@ -386,7 +400,6 @@ public void testTrustStoreReloadException() throws Exception {
386400
secureSettings.setString("xpack.security.transport.ssl.truststore.secure_password", "testnode");
387401
Settings settings = Settings.builder()
388402
.put("xpack.security.transport.ssl.truststore.path", trustStorePath)
389-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
390403
.put("path.home", createTempDir())
391404
.setSecureSettings(secureSettings)
392405
.build();
@@ -420,7 +433,6 @@ public void testPEMTrustReloadException() throws Exception {
420433
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
421434
Settings settings = Settings.builder()
422435
.putList("xpack.security.transport.ssl.certificate_authorities", clientCertPath.toString())
423-
.put("xpack.security.transport.ssl.supported_protocols", "TLSv1.2")
424436
.put("path.home", createTempDir())
425437
.build();
426438
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
@@ -490,7 +502,6 @@ private static MockWebServer getSslServer(Path keyStorePath, String keyStorePass
490502
}
491503
final SSLContext sslContext = new SSLContextBuilder()
492504
.loadKeyMaterial(keyStore, keyStorePass.toCharArray())
493-
.setProtocol("TLSv1.2")
494505
.build();
495506
MockWebServer server = new MockWebServer(sslContext, false);
496507
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
@@ -506,7 +517,6 @@ private static MockWebServer getSslServer(Path keyPath, Path certPath, String pa
506517
CertParsingUtils.readCertificates(Collections.singletonList(certPath)));
507518
final SSLContext sslContext = new SSLContextBuilder()
508519
.loadKeyMaterial(keyStore, password.toCharArray())
509-
.setProtocol("TLSv1.2")
510520
.build();
511521
MockWebServer server = new MockWebServer(sslContext, false);
512522
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
@@ -523,9 +533,8 @@ private static CloseableHttpClient getSSLClient(Path trustStorePath, String trus
523533
}
524534
final SSLContext sslContext = new SSLContextBuilder()
525535
.loadTrustMaterial(trustStore, null)
526-
.setProtocol("TLSv1.2")
527536
.build();
528-
return HttpClients.custom().setSSLContext(sslContext).build();
537+
return createHttpClient(sslContext);
529538
}
530539

531540
/**
@@ -543,9 +552,138 @@ private static CloseableHttpClient getSSLClient(List<Path> trustedCertificatePat
543552
}
544553
final SSLContext sslContext = new SSLContextBuilder()
545554
.loadTrustMaterial(trustStore, null)
546-
.setProtocol("TLSv1.2")
547555
.build();
548-
return HttpClients.custom().setSSLContext(sslContext).build();
556+
return createHttpClient(sslContext);
557+
}
558+
559+
private static CloseableHttpClient createHttpClient(SSLContext sslContext) {
560+
return HttpClients.custom()
561+
.setConnectionManager(new PoolingHttpClientConnectionManager(
562+
RegistryBuilder.<ConnectionSocketFactory>create()
563+
.register("http", PlainConnectionSocketFactory.getSocketFactory())
564+
.register("https", new SSLConnectionSocketFactory(sslContext, null, null, new DefaultHostnameVerifier()))
565+
.build(), getHttpClientConnectionFactory(), null, null, -1, TimeUnit.MILLISECONDS))
566+
.build();
567+
}
568+
569+
/**
570+
* Creates our own HttpConnectionFactory that changes how the connection is closed to prevent issues with
571+
* the MockWebServer going into an endless loop based on the way that HttpClient closes its connection.
572+
*/
573+
private static HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> getHttpClientConnectionFactory() {
574+
return (route, config) -> {
575+
ManagedHttpClientConnection delegate = ManagedHttpClientConnectionFactory.INSTANCE.create(route, config);
576+
return new ManagedHttpClientConnection() {
577+
@Override
578+
public String getId() {
579+
return delegate.getId();
580+
}
581+
582+
@Override
583+
public void bind(Socket socket) throws IOException {
584+
delegate.bind(socket);
585+
}
586+
587+
@Override
588+
public Socket getSocket() {
589+
return delegate.getSocket();
590+
}
591+
592+
@Override
593+
public SSLSession getSSLSession() {
594+
return delegate.getSSLSession();
595+
}
596+
597+
@Override
598+
public boolean isResponseAvailable(int timeout) throws IOException {
599+
return delegate.isResponseAvailable(timeout);
600+
}
601+
602+
@Override
603+
public void sendRequestHeader(HttpRequest request) throws HttpException, IOException {
604+
delegate.sendRequestHeader(request);
605+
}
606+
607+
@Override
608+
public void sendRequestEntity(HttpEntityEnclosingRequest request) throws HttpException, IOException {
609+
delegate.sendRequestEntity(request);
610+
}
611+
612+
@Override
613+
public HttpResponse receiveResponseHeader() throws HttpException, IOException {
614+
return delegate.receiveResponseHeader();
615+
}
616+
617+
@Override
618+
public void receiveResponseEntity(HttpResponse response) throws HttpException, IOException {
619+
delegate.receiveResponseEntity(response);
620+
}
621+
622+
@Override
623+
public void flush() throws IOException {
624+
delegate.flush();
625+
}
626+
627+
@Override
628+
public InetAddress getLocalAddress() {
629+
return delegate.getLocalAddress();
630+
}
631+
632+
@Override
633+
public int getLocalPort() {
634+
return delegate.getLocalPort();
635+
}
636+
637+
@Override
638+
public InetAddress getRemoteAddress() {
639+
return delegate.getRemoteAddress();
640+
}
641+
642+
@Override
643+
public int getRemotePort() {
644+
return delegate.getRemotePort();
645+
}
646+
647+
@Override
648+
public void close() throws IOException {
649+
if (delegate.getSocket() instanceof SSLSocket) {
650+
try (SSLSocket socket = (SSLSocket) delegate.getSocket()) {
651+
}
652+
}
653+
delegate.close();
654+
}
655+
656+
@Override
657+
public boolean isOpen() {
658+
return delegate.isOpen();
659+
}
660+
661+
@Override
662+
public boolean isStale() {
663+
return delegate.isStale();
664+
}
665+
666+
@Override
667+
public void setSocketTimeout(int timeout) {
668+
delegate.setSocketTimeout(timeout);
669+
}
670+
671+
@Override
672+
public int getSocketTimeout() {
673+
return delegate.getSocketTimeout();
674+
}
675+
676+
@Override
677+
public void shutdown() throws IOException {
678+
delegate.shutdown();
679+
}
680+
681+
@Override
682+
public HttpConnectionMetrics getMetrics() {
683+
return delegate.getMetrics();
684+
}
685+
};
686+
};
549687
}
550688

551689
private static void privilegedConnect(CheckedRunnable<Exception> runnable) throws Exception {

0 commit comments

Comments
 (0)