From 077b647ec4e59724cae2f71a2d8f329372b7342f Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Tue, 18 Dec 2018 15:51:19 -0500 Subject: [PATCH] Allow isSecure property of LoadBalancer ServiceInstance to determine the scheme of the URL. Fixes #641 --- .../main/asciidoc/spring-cloud-gateway.adoc | 7 ++ .../filter/LoadBalancerClientFilter.java | 2 +- .../filter/LoadBalancerClientFilterTests.java | 64 +++++++++++++++++-- .../resources/application-multi-cert-ssl.yml | 2 +- .../resources/application-single-cert-ssl.yml | 2 +- 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 5897035c..83387096 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -876,6 +876,13 @@ spring: - Path=/service/** ---- +NOTE: The `isSecure` value of the `ServiceInstance` returned from the `LoadBalancer` will override +the scheme specified in the request made to the Gateway. For example, if the request comes into the Gateway over `HTTPS` +but the `ServiceInstance` indicates it is not secure, then the downstream request will be made over +`HTTP`. The opposite situation can also apply. However if `GATEWAY_SCHEME_PREFIX_ATTR` is specified for the +route in the Gateway configuration, the prefix will be stripped and the resulting scheme from the +route URL will override the `ServiceInstance` configuration. + === Netty Routing Filter The Netty Routing Filter runs if the url located in the `ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR` exchange attribute has a `http` or `https` scheme. It uses the Netty `HttpClient` to make the downstream proxy request. The response is put in the `ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR` exchange attribute for use in a later filter. (There is an experimental `WebClientHttpRoutingFilter` that performs the same function, but does not require netty) diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilter.java index ff12c753..13254c6c 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilter.java @@ -76,7 +76,7 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered { // if the `lb:` mechanism was used, use `` as the default, // if the loadbalancer doesn't provide one. - String overrideScheme = null; + String overrideScheme = instance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilterTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilterTests.java index d3f45095..7df919a1 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilterTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerClientFilterTests.java @@ -123,6 +123,58 @@ public class LoadBalancerClientFilterTests { verifyNoMoreInteractions(chain); } + @Test + public void instanceOverrideNonSecureScheme() { + MockServerHttpRequest request = MockServerHttpRequest + .get("https://localhost") + .build(); + + URI lbUri = URI.create("lb://service1"); + ServerWebExchange webExchange = testFilter(request, lbUri); + URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(uri).hasScheme("http").hasHost("service1-host1"); + } + + @Test + public void instanceOverrideSecureScheme() { + MockServerHttpRequest request = MockServerHttpRequest + .get("http://localhost") + .build(); + + URI lbUri = URI.create("lb://service1"); + ServerWebExchange webExchange = testFilter(request, lbUri, 443); + URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(uri).hasScheme("https").hasHost("service1-host1"); + } + + @Test + public void instanceOverrideSecureSchemePrefix() { + MockServerHttpRequest request = MockServerHttpRequest + .get("https://localhost") + .build(); + + URI lbUri = URI.create("http://service1"); + ServerWebExchange exchange = MockServerWebExchange.from(request); + exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb"); + ServerWebExchange webExchange = testFilter(exchange, lbUri, 443); + URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(uri).hasScheme("http").hasHost("service1-host1"); + } + + @Test + public void instanceOverrideNonSecureSchemePrefix() { + MockServerHttpRequest request = MockServerHttpRequest + .get("http://localhost") + .build(); + + URI lbUri = URI.create("https://service1"); + ServerWebExchange exchange = MockServerWebExchange.from(request); + exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb"); + ServerWebExchange webExchange = testFilter(exchange, lbUri, 8081); + URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(uri).hasScheme("https").hasHost("service1-host1"); + } + @Test public void happyPath() { @@ -208,7 +260,7 @@ public class LoadBalancerClientFilterTests { exchange = MockServerWebExchange.from(request); exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb"); - ServerWebExchange webExchange = testFilter(exchange, lbUri); + ServerWebExchange webExchange = testFilter(exchange, lbUri, 8081); URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); assertThat(uri).hasScheme("ws").hasHost("service1-host1") .hasParameter("a", "b"); @@ -274,10 +326,14 @@ public class LoadBalancerClientFilterTests { } private ServerWebExchange testFilter(MockServerHttpRequest request, URI uri) { - return testFilter(MockServerWebExchange.from(request), uri); + return testFilter(MockServerWebExchange.from(request), uri, 8081); } - private ServerWebExchange testFilter(ServerWebExchange exchange, URI uri) { + private ServerWebExchange testFilter(MockServerHttpRequest request, URI uri, int port) { + return testFilter(MockServerWebExchange.from(request), uri, port); + } + + private ServerWebExchange testFilter(ServerWebExchange exchange, URI uri, int port) { exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); @@ -288,7 +344,7 @@ public class LoadBalancerClientFilterTests { when(clientFactory.getLoadBalancerContext("service1")).thenReturn(new RibbonLoadBalancerContext(loadBalancer)); when(clientFactory.getLoadBalancer("service1")).thenReturn(loadBalancer); - when(loadBalancer.chooseServer(any())).thenReturn(new Server("service1-host1", 8081)); + when(loadBalancer.chooseServer(any())).thenReturn(new Server("service1-host1", port)); RibbonLoadBalancerClient client = new RibbonLoadBalancerClient(clientFactory); diff --git a/spring-cloud-gateway-core/src/test/resources/application-multi-cert-ssl.yml b/spring-cloud-gateway-core/src/test/resources/application-multi-cert-ssl.yml index 52e21ea8..60c3d363 100644 --- a/spring-cloud-gateway-core/src/test/resources/application-multi-cert-ssl.yml +++ b/spring-cloud-gateway-core/src/test/resources/application-multi-cert-ssl.yml @@ -1,6 +1,6 @@ test: hostport: httpbin.org:80 - uri: lb://testservice + uri: lb:https://testservice server: ssl: diff --git a/spring-cloud-gateway-core/src/test/resources/application-single-cert-ssl.yml b/spring-cloud-gateway-core/src/test/resources/application-single-cert-ssl.yml index a1da9aa1..7c184e4f 100644 --- a/spring-cloud-gateway-core/src/test/resources/application-single-cert-ssl.yml +++ b/spring-cloud-gateway-core/src/test/resources/application-single-cert-ssl.yml @@ -1,6 +1,6 @@ test: hostport: httpbin.org:80 - uri: lb://testservice + uri: lb:https://testservice server: ssl: