Browse Source

Merge pull request #437 from tony-clarke-amdocs/ssl-trust

Create a trust manager backed by known trusted X509 certificates
pull/457/head
Ryan Baxter 7 years ago committed by GitHub
parent
commit
1b9f5eea6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      docs/src/main/asciidoc/spring-cloud-gateway.adoc
  2. 21
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
  3. 50
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java
  4. 93
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLTests.java
  5. 38
      spring-cloud-gateway-core/src/test/resources/application-ssl.yml
  6. 27
      spring-cloud-gateway-core/src/test/resources/scg-cert.pem
  7. BIN
      spring-cloud-gateway-core/src/test/resources/scg-keystore.p12

44
docs/src/main/asciidoc/spring-cloud-gateway.adoc

@ -905,6 +905,50 @@ or check if an exchange has already been routed. @@ -905,6 +905,50 @@ or check if an exchange has already been routed.
* `ServerWebExchangeUtils.isAlreadyRouted` takes a `ServerWebExchange` object and checks if it has been "routed"
* `ServerWebExchangeUtils.setAlreadyRouted` takes a `ServerWebExchange` object and marks it as "routed"
== TLS / SSL
The Gateway can listen for requests on https by following the usual Spring server configuration. Example:
.application.yml
[source,yaml]
----
server:
ssl:
enabled: true
key-alias: scg
key-store-password: scg1234
key-store: classpath:scg-keystore.p12
key-store-type: PKCS12
----
Gateway routes can be routed to both http and https backends. If routing to a https backend then the Gateway can be configured to trust all downstream certificates with the following configuration:
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
httpclient:
ssl:
useInsecureTrustManager: true
----
Using an insecure trust manager is not suitable for production. For a production deployment the Gateway can be configured with a set of known certificates that it can trust with the follwing configuration:
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
httpclient:
ssl:
trustedX509Certificates:
- cert1.pem
- cert2.pem
----
== Configuration
Configuration for Spring Cloud Gateway is driven by a collection of `RouteDefinitionLocator`s.

21
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@ -17,6 +17,12 @@ @@ -17,6 +17,12 @@
package org.springframework.cloud.gateway.config;
import java.io.IOException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@ -45,6 +51,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -45,6 +51,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter;
import org.springframework.cloud.gateway.filter.ForwardPathFilter;
@ -121,6 +128,7 @@ import org.springframework.context.annotation.Configuration; @@ -121,6 +128,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.DispatcherHandler;
@ -162,10 +170,17 @@ public class GatewayAutoConfiguration { @@ -162,10 +170,17 @@ public class GatewayAutoConfiguration {
// configure ssl
HttpClientProperties.Ssl ssl = properties.getSsl();
if (ssl.isUseInsecureTrustManager()) {
X509Certificate[] trustedX509Certificates = ssl
.getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {
opts.sslSupport(sslContextBuilder -> {
sslContextBuilder.trustManager(trustedX509Certificates);
});
}
else if (ssl.isUseInsecureTrustManager()) {
opts.sslSupport(sslContextBuilder -> {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
sslContextBuilder
.trustManager(InsecureTrustManagerFactory.INSTANCE);
});
}

50
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java

@ -18,9 +18,19 @@ @@ -18,9 +18,19 @@
package org.springframework.cloud.gateway.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.util.ResourceUtils;
import reactor.ipc.netty.resources.PoolResources;
import java.io.IOException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* Configuration properties for the Netty {@link reactor.ipc.netty.http.client.HttpClient}
@ -210,6 +220,41 @@ public class HttpClientProperties { @@ -210,6 +220,41 @@ 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<>();
public List<String> getTrustedX509Certificates() {
return trustedX509Certificates;
}
public X509Certificate[] getTrustedX509CertificatesForTrustManager() {
try {
CertificateFactory certificateFactory = CertificateFactory
.getInstance("X.509");
ArrayList<X509Certificate> certs = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
X509Certificate cert = (X509Certificate) certificateFactory
.generateCertificate(url.openStream());
certs.add(cert);
}
catch (IOException e) {
throw new WebServerException(
"Could not load certificate '" + trustedCert + "'", e);
}
}
return certs.toArray(new X509Certificate[certs.size()]);
}
catch (CertificateException e1) {
throw new WebServerException("Could not load CertificateFactory X.509",
e1);
}
}
public void setTrustedX509Certificates(List<String> trustedX509) {
this.trustedX509Certificates = trustedX509;
}
//TODO: support configuration of other trust manager factories
@ -223,9 +268,8 @@ public class HttpClientProperties { @@ -223,9 +268,8 @@ public class HttpClientProperties {
@Override
public String toString() {
return "Ssl{" +
"useInsecureTrustManager=" + useInsecureTrustManager +
'}';
return "Ssl {useInsecureTrustManager=" + useInsecureTrustManager
+ ", trustedX509Certificates=" + trustedX509Certificates + "}";
}
}

93
spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLTests.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* 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.junit.Assert.assertTrue;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import javax.net.ssl.SSLException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
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.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;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
@ActiveProfiles("ssl")
public class SSLTests extends BaseWebClientTests {
@Before
public void setup() {
try {
SslContext sslContext = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(
opt -> opt.sslContext(sslContext));
baseUri = "https://localhost:" + port;
this.webClient = WebClient.builder().clientConnector(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());
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
@RestController
public static class TestConfig {
@RequestMapping("/httpbin/ssltrust")
public ResponseEntity<Void> nocontenttype() {
return ResponseEntity.status(204).build();
}
}
}

38
spring-cloud-gateway-core/src/test/resources/application-ssl.yml

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
test:
hostport: httpbin.org:80
uri: lb://testservice
server:
ssl:
enabled: true
key-alias: scg
key-store-password: scg1234
key-store: classpath:scg-keystore.p12
key-store-type: PKCS12
spring:
cloud:
gateway:
httpclient:
ssl:
trustedX509Certificates:
- src/test/resources/scg-cert.pem
default-filters:
- PrefixPath=/httpbin
routes:
- id: default_path_to_httpbin
uri: ${test.uri}
order: 10000
predicates:
- name: Path
args:
pattern: /**
logging:
level:
org.springframework.cloud.gateway: TRACE
org.springframework.http.server.reactive: DEBUG
org.springframework.web.reactive: DEBUG
reactor.ipc.netty: DEBUG
redisratelimiter: DEBUG

27
spring-cloud-gateway-core/src/test/resources/scg-cert.pem

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEjTCCAvWgAwIBAgIEIZEBKTANBgkqhkiG9w0BAQwFADBsMRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du
MB4XDTE4MDcxODEyNTY0OFoXDTQ1MTIwMzEyNTY0OFowbDEQMA4GA1UEBhMHVW5r
bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE
ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC
AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJhdOwHjozNaUdocb+p/Dn6E
UUXzkNGwmUZwRSQKnEK9IV/PTwrzK+fPJS8vpUzEpj37QIGBTSNuebQ3w5G4cAu6
xFEk4e56C823Vio5Hae/+fos7goaR+ihKMy2lGPwFGNECqGmUZItE1+4pdjYyia7
FTWluAVuTgue3VODl9ziDw+2Zrt6Axtnb5heQXP8ElEZldoePpUmbLWa03Xvfatb
4ZKkoCLOKhcgM5F2tyR+VlZnqm2dhvO+J778MsU4ToVAUGrVIkeQi0BHTi5Rzy2c
2kDLHHuJTYqN65sl5LyKDu003KXioelZVUeH3Hgtrj0Tt96oYSixvHSGwDmcXpux
4sbzTS7x/KeQAjLIFj/3rxQP0TSuTCe/XrqiCpFflLntTI9En9CWto6SXFRigval
zCITgImQthSfFMxFDqNtNQo5hTAu8VY2/FurMbkukQ5l2+vU4dKBccNxChj5m7p3
8C4G7Rh08AEYaNYcNZlaz++c37rW7P6vWX8m6C21CQIDAQABozcwNTAUBgNVHREE
DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFDOSE31Vw2QdyVviz+H5+tY5HRR4MA0G
CSqGSIb3DQEBDAUAA4IBgQAJljlIubbQvw/2UVymsyF939XKus/7muiJLtQt0J4J
sSuGiSQmkyJajBRj9+qsLLTtdL6F2+BFJtP8S/zZixrEzktuRZ3b40MLtyI4hVDt
fmgj3UMmphVgbmDv71WvRmUXFfBX5Zka7zRW+lFvO5dLytegKi3Vc8zOFRUcvY9w
uBEOipAkUqH6y+lI/lJ72MrXpkxbkAq2fYffZK4e6KNpKG7pP0txEkwNvosKSAjA
yL+Ye5bK6sYbscwsqvXw9nWwjuFpj/0zvD/tM1jMtUtV1+9HKC3vsfAaL9QnwnS6
qhSaos8j7fw9SYdAoO+yth2w1ETxKXrzcF+LEkhzj2R0zygycLZvWj51lIWDDOvy
m3IUFTY8fX7CBgLIGnHAcdWK6a+FkWIb8WybWkv2n2SxM4AnzXb5epk/K2Uo30hY
mr9wrywvo92xUjXEOmQpAbFx0hcVjYOtvpHdUeZTGLLQ7sWJGQFalRT+GsTy+Lph
10719GizKXQW2Mx/8XOr1Ow=
-----END CERTIFICATE-----

BIN
spring-cloud-gateway-core/src/test/resources/scg-keystore.p12

Binary file not shown.
Loading…
Cancel
Save