Merge branch '2.0.x' of github.com:spring-cloud/spring-cloud-gateway into 2.0.x

This commit is contained in:
Ryan Baxter
2018-10-15 13:52:23 -04:00
18 changed files with 441 additions and 37 deletions

View File

@@ -953,6 +953,24 @@ spring:
- cert2.pem
----
If the Spring Cloud Gateway is not provisioned with trusted certificates the default trust store is used (which can be overriden with system property javax.net.ssl.trustStore).
=== 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

@@ -25,11 +25,13 @@ import reactor.ipc.netty.resources.PoolResources;
import java.io.IOException;
import java.net.URL;
import java.security.cert.Certificate;
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.Collection;
import java.util.List;
/**
@@ -220,8 +222,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;
@@ -231,20 +238,20 @@ public class HttpClientProperties {
try {
CertificateFactory certificateFactory = CertificateFactory
.getInstance("X.509");
ArrayList<X509Certificate> certs = new ArrayList<>();
ArrayList<Certificate> allCerts = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
X509Certificate cert = (X509Certificate) certificateFactory
.generateCertificate(url.openStream());
certs.add(cert);
Collection<? extends Certificate> certs = certificateFactory
.generateCertificates(url.openStream());
allCerts.addAll(certs);
}
catch (IOException e) {
throw new WebServerException(
"Could not load certificate '" + trustedCert + "'", e);
}
}
return certs.toArray(new X509Certificate[certs.size()]);
return allCerts.toArray(new X509Certificate[allCerts.size()]);
}
catch (CertificateException e1) {
throw new WebServerException("Could not load CertificateFactory X.509",
@@ -266,11 +273,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

@@ -43,6 +43,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest;
@@ -131,8 +132,9 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
if (headers.getContentType() != null) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, headers.getContentType());
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
}
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(

View File

@@ -75,7 +75,12 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
.retain() //TODO: needed?
.map(factory::wrap);
MediaType contentType = response.getHeaders().getContentType();
MediaType contentType = null;
try {
contentType = response.getHeaders().getContentType();
} catch (Exception e) {
log.trace("invalid media type", e);
}
return (isStreamingMediaType(contentType) ?
response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
}));

View File

@@ -68,11 +68,14 @@ public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.uri(routeUri)
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme())
.host(routeUri.getHost())
.port(routeUri.getPort())
.build(encoded)
.toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
return chain.filter(exchange);
}

View File

@@ -30,6 +30,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
@@ -42,7 +43,6 @@ import com.netflix.hystrix.exception.HystrixRuntimeException;
import static com.netflix.hystrix.exception.HystrixRuntimeException.FailureType.TIMEOUT;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
import reactor.core.publisher.Mono;
import rx.Observable;
@@ -101,9 +101,8 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
}).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
if (throwable instanceof HystrixRuntimeException) {
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
if (e.getFailureType() == TIMEOUT) { //TODO: optionally set status
setResponseStatus(exchange, HttpStatus.GATEWAY_TIMEOUT);
return exchange.getResponse().setComplete();
if (e.getFailureType() == TIMEOUT) {
return Mono.error(new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT));
}
}
return Mono.error(throwable);
@@ -191,4 +190,5 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
return this;
}
}
}

View File

@@ -84,9 +84,11 @@ public class ModifyResponseBodyGatewayFilterFactory
Class inClass = config.getInClass();
Class outClass = config.getOutClass();
MediaType originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(originalResponseContentType);
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());

View File

@@ -48,9 +48,10 @@ public class RouteToRequestUrlFilterTests {
.get("http://localhost/get?a=b")
.build();
ServerWebExchange webExchange = testFilter(request, "http://myhost");
ServerWebExchange webExchange = testFilter(request, "http://myhost/mypath");
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("myhost")
.hasPath("/get")
.hasParameter("a", "b");
}
@@ -60,6 +61,17 @@ public class RouteToRequestUrlFilterTests {
.get("http://localhost/getb")
.build();
ServerWebExchange webExchange = testFilter(request, "lb://myhost");
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("lb").hasHost("myhost");
}
@Test
public void happyPathLbPlusScheme() {
MockServerHttpRequest request = MockServerHttpRequest
.get("http://localhost/getb")
.build();
ServerWebExchange webExchange = testFilter(request, "lb:http://myhost");
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("myhost");
@@ -114,7 +126,7 @@ public class RouteToRequestUrlFilterTests {
.method(HttpMethod.GET, url)
.build();
ServerWebExchange webExchange = testFilter(request, "http://myhost");
ServerWebExchange webExchange = testFilter(request, "http://myhost/abc%20def/get");
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("myhost")
.hasPath("/abc def/get");
@@ -170,9 +182,9 @@ public class RouteToRequestUrlFilterTests {
}
}
private ServerWebExchange testFilter(MockServerHttpRequest request, String url) {
private ServerWebExchange testFilter(MockServerHttpRequest request, String routeUri) {
Route value = Route.async().id("1")
.uri(URI.create(url))
.uri(URI.create(routeUri))
.order(0)
.predicate(swe -> true)
.build();

View File

@@ -40,11 +40,14 @@ import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.http.MediaType.TEXT_HTML;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@@ -65,7 +68,10 @@ public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests {
testClient.get().uri("/delay/3")
.header("Host", "www.hystrixfailure.org")
.exchange()
.expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
.expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT)
.expectBody()
.jsonPath("$.status")
.isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value()));
}
@Test
@@ -103,6 +109,23 @@ public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests {
.expectStatus().is5xxServerError();
}
@Test
public void hystrixFilterErrorPage() {
testClient.get().uri("/delay/3")
.header("Host", "www.hystrixconnectfail.org")
.accept(TEXT_HTML)
.exchange()
.expectStatus().is5xxServerError()
.expectBody().consumeWith(res -> {
final String body = new String(res.getResponseBody(), UTF_8);
Assert.isTrue(body.contains("<h1>Whitelabel Error Page</h1>"),
"Cannot find the expected white-label error page title in the response");
Assert.isTrue(body.contains("(type=Internal Server Error, status=500)"),
"Cannot find the expected error status report in the response");
});
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)

View File

@@ -0,0 +1,120 @@
/*
* 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.filter.headers;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(
webEnvironment = DEFINED_PORT,
properties = {"server.port=62175"}
)
@DirtiesContext
public class NonStandardHeadersInResponseTests extends BaseWebClientTests {
public static final String CONTENT_TYPE_IMAGE = "image";
@Test
public void nonStandardHeadersInResponse() {
URI uri = UriComponentsBuilder
.fromUriString(this.baseUri + "/get-image")
.build(true)
.toUri();
String contentType = WebClient.builder()
.baseUrl(baseUri)
.build()
.get()
.uri(uri)
.exchange()
.map(clientResponse -> clientResponse.headers().asHttpHeaders().getFirst(HttpHeaders.CONTENT_TYPE))
.block();
assertEquals(CONTENT_TYPE_IMAGE, contentType);
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
private static final Log log = LogFactory.getLog(TestConfig.class);
@Value("${test.uri}")
String uri;
@Value("${server.port}")
int port;
@Bean
@Order(5001)
public GlobalFilter addNonStandardHeaderFilter() {
return (exchange, chain) -> {
log.info("addNonStandardHeaderFilter pre phase");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("addNonStandardHeaderFilter post phase");
List<String> contentTypes = exchange.getResponse().getHeaders().get(HttpHeaders.CONTENT_TYPE);
contentTypes.clear();
contentTypes.add(CONTENT_TYPE_IMAGE);
}));
};
}
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("non_standard_header_route", r ->
r.path("/get-image/**")
.filters(f -> f
.addRequestHeader(HttpHeaders.HOST, "www.addrequestparameter.org")
.stripPrefix(1)
)
.uri("http://localhost:" + port + "/get"))
.route("internal_route", r ->
r.path("/get/**")
.filters(f -> f.prefixPath("/httpbin"))
.uri(uri))
.build();
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.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;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
@ActiveProfiles("multi-cert-ssl")
public class MultiCertSSLTests extends SingleCertSSLTests {
}

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 SingleCertSSLTests {
@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;
@@ -49,8 +47,8 @@ import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
@ActiveProfiles("ssl")
public class SSLTests extends BaseWebClientTests {
@ActiveProfiles("single-cert-ssl")
public class SingleCertSSLTests extends BaseWebClientTests {
@Before
public void setup() {
@@ -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

View File

@@ -16,7 +16,7 @@ spring:
httpclient:
ssl:
trustedX509Certificates:
- src/test/resources/scg-cert.pem
- src/test/resources/multi-cert.pem
default-filters:
- PrefixPath=/httpbin
routes:

View File

@@ -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/single-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

View File

@@ -0,0 +1,62 @@
-----BEGIN CERTIFICATE-----
MIIGKzCCBROgAwIBAgIQByMt0ja6e5B8OohEG0rsHzANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgwMzE2MDAwMDAwWhcN
MTkwMzIxMTIwMDAwWjBtMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQKExZQaXZvdGFsIFNvZnR3YXJl
LCBJbmMuMRQwEgYDVQQDDAsqLnNwcmluZy5pbzCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANjtAeOmtB/ZPoX5r2MDWPmLVaLHFBLUViLsUMInkfpakesg
bl6vkWaVZpvarbOxzHuiYjsXEjlgpZTYVguv3UZHoDoD5k/E9k0N6UBdx5edXnsc
Ssty/fuH60MkDAsw89AILN4MAlkG2DIRJfGZGqCfA2k9Z35tz9l0OEavfqxNlUyT
X3AkCDyiEl5bfo2XevNw46O/21TKHwE1uDWszH2AOszv5TxdyICzCZUmjesb0awe
9PB6Mlv83U+MZgWS9SoHlQjfpz+tUVGm3PnHQ6Oqo4nJVb+wbgQ6aXyOpo/W5GQE
5svJcsF3AoDalPIsGU66EMYrooscUnoiDitViOkCAwEAAaOCAuUwggLhMB8GA1Ud
IwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBSDcxmAlUjp2j+3
9OZAjqL6dY8AqTAhBgNVHREEGjAYggsqLnNwcmluZy5pb4IJc3ByaW5nLmlvMA4G
A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYD
VR0fBGQwYjAvoC2gK4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hh
Mi1nNi5jcmwwL6AtoCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNo
YTItZzYuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEW
HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMHwGCCsGAQUF
BwEBBHAwbjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEYG
CCsGAQUFBzAChjpodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRT
SEEyU2VjdXJlU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggEEBgorBgEEAdZ5
AgQCBIH1BIHyAPAAdQCkuQmQtBhYFIe7E6LMZ3AKPDWYBPkb37jjd80OyA3cEAAA
AWIwOqM0AAAEAwBGMEQCIBr9GyZ5+YtlqoKhV/exaukHme/YbpXl/O1+6sG/kOro
AiBEI3IfRziaa9QMlm0fp2wGSbhpArTQ03w5Yq795QAhtgB3AG9Tdqwx8DEZ2JkA
pFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABYjA6pHYAAAQDAEgwRgIhAMyltKcFRCoE
BSoeBUd8OFU7gKlWKsmxAZkVKpLuYbMKAiEAgb7snjGCWpX1qRdg+TlKnyn3bocK
i2pROA/1lzAOqzowDQYJKoZIhvcNAQELBQADggEBACDyzGb8YxHFQBO20l7LYbW8
rNat/FhB5H8JCEaup7pvdVCNvyUOOR3fm+by+a56QI1cb0vx8N6+j/rVXccniL88
26FGO2IQeAnNa0Mc8ANJdLrYzwCSiObIFXh2/Cu86oe7SJNjbxJpX3C2OKxmHVVP
AofQsz59V5O1JBkblRAfK9yBOroeBAMRvNxHuolleuip4IKD6URGXQzDyXqNbWs7
O3zyXtGAoCUAxUtoOcZNZWvXF3eYFxMnEmsuNjO6UHtfvJGB6pqjWp/00j5cioE3
RLqeByg4E9a9iM5onXdSSGzAYusdZE53IAezV0w8KNJuOKHYLwpO59tvU901OK0=
-----END CERTIFICATE-----
-----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-----