Add support for configuring TLS handshake timeouts

This commit is contained in:
Tony Clarke
2018-09-29 14:54:48 -04:00
parent c12506fa2f
commit 2444776250
5 changed files with 110 additions and 12 deletions

View File

@@ -950,6 +950,22 @@ spring:
- cert2.pem
----
=== TLS Handshake
The Gateway maintains a client pool that it uses to route to backends. When communicating over https the client initiates a TLS handshake. A number of timeouts are assoicated with this handshake. These timeouts can be configured (defaults shown):
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
httpclient:
ssl:
handshake-timeout-millis: 10000
close-notify-flush-timeout-millis: 3000
close-notify-read-timeout-millis: 0
----
== Configuration

View File

@@ -166,6 +166,9 @@ public class GatewayAutoConfiguration {
// configure ssl
HttpClientProperties.Ssl ssl = properties.getSsl();
opts.sslHandshakeTimeoutMillis(ssl.getHandshakeTimeoutMillis());
opts.sslCloseNotifyFlushTimeoutMillis(ssl.getCloseNotifyFlushTimeoutMillis());
opts.sslCloseNotifyReadTimeoutMillis(ssl.getCloseNotifyReadTimeoutMillis());
X509Certificate[] trustedX509Certificates = ssl
.getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {

View File

@@ -220,8 +220,13 @@ public class HttpClientProperties {
public class Ssl {
/** Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production. */
private boolean useInsecureTrustManager = false;
private List<String> trustedX509Certificates = new ArrayList<>();
// use netty default SSL timeouts
private long handshakeTimeoutMillis = 10000L;
private long closeNotifyFlushTimeoutMillis = 3000L;
private long closeNotifyReadTimeoutMillis = 0L;
public List<String> getTrustedX509Certificates() {
return trustedX509Certificates;
@@ -266,11 +271,42 @@ public class HttpClientProperties {
this.useInsecureTrustManager = useInsecureTrustManager;
}
public long getHandshakeTimeoutMillis() {
return handshakeTimeoutMillis;
}
public void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) {
this.handshakeTimeoutMillis = handshakeTimeoutMillis;
}
public long getCloseNotifyFlushTimeoutMillis() {
return closeNotifyFlushTimeoutMillis;
}
public void setCloseNotifyFlushTimeoutMillis(long closeNotifyFlushTimeoutMillis) {
this.closeNotifyFlushTimeoutMillis = closeNotifyFlushTimeoutMillis;
}
public long getCloseNotifyReadTimeoutMillis() {
return closeNotifyReadTimeoutMillis;
}
public void setCloseNotifyReadTimeoutMillis(long closeNotifyReadTimeoutMillis) {
this.closeNotifyReadTimeoutMillis = closeNotifyReadTimeoutMillis;
}
@Override
public String toString() {
return "Ssl {useInsecureTrustManager=" + useInsecureTrustManager
+ ", trustedX509Certificates=" + trustedX509Certificates + "}";
+ ", trustedX509Certificates=" + trustedX509Certificates
+ ", handshakeTimeoutMillis=" + handshakeTimeoutMillis
+ ", closeNotifyFlushTimeoutMillis="
+ closeNotifyFlushTimeoutMillis
+ ", closeNotifyReadTimeoutMillis="
+ closeNotifyReadTimeoutMillis + "}";
}
}
@Override

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.gateway.test.ssl;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.JsonPathAssertions;
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
@RunWith(SpringRunner.class)
// this test works because it assumes TLS hand shake cannot be done in 1ms. It takes closer to 80ms
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {"spring.cloud.gateway.httpclient.ssl.handshake-timeout-millis=1"})
@DirtiesContext
@ActiveProfiles("ssl")
public class SSLHandshakeTimeoutTests extends SSLTests {
@Test
@Override // here we validate that it the handshake times out
public void testSslTrust() {
ResponseSpec responseSpec = testClient.get().uri("/ssltrust").exchange();
responseSpec.expectStatus().is5xxServerError();
JsonPathAssertions jsonPath = responseSpec.expectBody().jsonPath("message");
jsonPath.isEqualTo("handshake timed out");
}
}

View File

@@ -17,7 +17,6 @@
package org.springframework.cloud.gateway.test.ssl;
import static org.junit.Assert.assertTrue;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import javax.net.ssl.SSLException;
@@ -30,16 +29,15 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.handler.ssl.SslContext;
@@ -61,20 +59,17 @@ public class SSLTests extends BaseWebClientTests {
opt -> opt.sslContext(sslContext));
baseUri = "https://localhost:" + port;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(baseUri).build();
.baseUrl(baseUri).build();
this.testClient = WebTestClient.bindToServer(httpConnector).baseUrl(baseUri).build();
}
catch (SSLException e) {
throw new RuntimeException(e);
}
}
@Test
public void testSslTrust() {
ClientResponse clientResponse = webClient.get().uri("/ssltrust")
.exchange().block();
HttpStatus statusCode = clientResponse.statusCode();
assertTrue(statusCode.is2xxSuccessful());
testClient.get().uri("/ssltrust").exchange().expectStatus().is2xxSuccessful();
}
@EnableAutoConfiguration