From 4ae403148ea330da0519d006a87cb2b33538b8a2 Mon Sep 17 00:00:00 2001 From: Benjamin Einaudi Date: Tue, 22 Feb 2022 21:42:10 +0100 Subject: [PATCH] [spring-cloud-config] Allow to use other scheme proxy when only one proxy is configured (#2049) Allow to specify 'http' proxy for both protocols and viceversa Close #2048 --- .../main/asciidoc/spring-cloud-config.adoc | 6 +- .../server/proxy/SchemeBasedRoutePlanner.java | 18 ++-- ...ttpClientVaultRestTemplateFactoryTest.java | 13 +-- ...HttpConnectionFactoryIntegrationTests.java | 12 +-- .../proxy/SchemeBasedRoutePlannerTest.java | 99 +++++++++++++++++++ 5 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlannerTest.java diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index f38271f3..3467b400 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -740,7 +740,11 @@ When `myApp` has the `dev` profile enabled, properties written to all of the abo ==== Accessing Backends Through a Proxy -The configuration server can access a Git or Vault backend through an HTTP or HTTPS proxy. This behavior is controlled for either Git or Vault by settings under `proxy.http` and `proxy.https`. These settings are per repository, so if you are using a <> you must configure proxy settings for each backend in the composite individually. If using a network which requires separate proxy servers for HTTP and HTTPS URLs, you can configure both the HTTP and the HTTPS proxy settings for a single backend. +The configuration server can access a Git or Vault backend through an HTTP or HTTPS proxy. +This behavior is controlled for either Git or Vault by settings under `proxy.http` and `proxy.https`. +These settings are per repository, so if you are using a <> you must configure proxy settings for each backend in the composite individually. +If using a network which requires separate proxy servers for HTTP and HTTPS URLs, you can configure both the HTTP and the HTTPS proxy settings for a single backend: in this case `http` access will use `http` proxy and `https` access the `https` one. +Also, you may specify one sole proxy that will be used for both protocols using the proxy definition protocol between application and proxy. The following table describes the proxy configuration properties for both HTTP and HTTPS proxies. All of these properties must be prefixed by `proxy.http` or `proxy.https`. diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlanner.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlanner.java index 008055e2..77fa0bc8 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlanner.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlanner.java @@ -26,23 +26,27 @@ import org.apache.http.protocol.HttpContext; */ public class SchemeBasedRoutePlanner extends DefaultRoutePlanner { - private final ProxyHostProperties httpsProxy; + private final HttpHost httpsProxy; - private final ProxyHostProperties httpProxy; + private final HttpHost defaultSchemeProxy; public SchemeBasedRoutePlanner(ProxyHostProperties httpsProxy, ProxyHostProperties httpProxy) { super(null); - this.httpsProxy = httpsProxy; - this.httpProxy = httpProxy; + this.httpsProxy = buildProxy(httpsProxy, "https"); + this.defaultSchemeProxy = buildProxy(httpProxy, HttpHost.DEFAULT_SCHEME_NAME); } @Override protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) { - return "https".equals(target.getSchemeName()) ? determineProxy(this.httpsProxy, "https") - : determineProxy(this.httpProxy, HttpHost.DEFAULT_SCHEME_NAME); + return "https".equals(target.getSchemeName()) ? determineProxy(this.httpsProxy, this.defaultSchemeProxy) + : determineProxy(this.defaultSchemeProxy, this.httpsProxy); } - private HttpHost determineProxy(ProxyHostProperties properties, String scheme) { + private HttpHost determineProxy(HttpHost proxy, HttpHost fallbackProxy) { + return proxy != null ? proxy : fallbackProxy; + } + + private HttpHost buildProxy(ProxyHostProperties properties, String scheme) { if (properties == null) { return null; } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/HttpClientVaultRestTemplateFactoryTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/HttpClientVaultRestTemplateFactoryTest.java index 1e101040..f4da00d5 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/HttpClientVaultRestTemplateFactoryTest.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/HttpClientVaultRestTemplateFactoryTest.java @@ -103,15 +103,16 @@ public class HttpClientVaultRestTemplateFactoryTest { } @Test - public void httpsProxy_notCalled() throws Exception { + public void httpsProxy_called_for_http_request_when_no_httpProxy_specified() throws Exception { VaultEnvironmentProperties properties = getVaultEnvironmentProperties(null, HTTPS_PROXY); RestTemplate restTemplate = this.factory.build(properties); - this.expectedException.expectCause( - allOf(instanceOf(UnknownHostException.class), hasProperty("message", containsString("somehost")))); + this.expectedException.expectCause(allOf(instanceOf(UnknownHostException.class), + hasProperty("message", containsString(HTTPS_PROXY.getHost())))); restTemplate.getForObject("http://somehost", String.class); } + @Test public void authenticatedHttpProxy() throws Exception { VaultEnvironmentProperties properties = getVaultEnvironmentProperties(AUTHENTICATED_HTTP_PROXY, null); @@ -133,11 +134,11 @@ public class HttpClientVaultRestTemplateFactoryTest { } @Test - public void httpProxy_notCalled() throws Exception { + public void httpProxy_called_for_https_request_when_no_httpsProxy_specified() throws Exception { VaultEnvironmentProperties properties = getVaultEnvironmentProperties(HTTP_PROXY, null); RestTemplate restTemplate = this.factory.build(properties); - this.expectedException.expectCause( - allOf(instanceOf(UnknownHostException.class), hasProperty("message", containsString("somehost")))); + this.expectedException.expectCause(allOf(instanceOf(UnknownHostException.class), + hasProperty("message", containsString(HTTP_PROXY.getHost())))); restTemplate.getForObject("https://somehost", String.class); } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactoryIntegrationTests.java index 68a2ac1e..60af4288 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactoryIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/ConfigurableHttpConnectionFactoryIntegrationTests.java @@ -135,13 +135,13 @@ public class ConfigurableHttpConnectionFactoryIntegrationTests { } @Test - public void httpsProxy_notCalled() throws Exception { + public void httpsProxy_called_for_http_request_when_no_httpProxy_specified() throws Exception { String repoUrl = "https://myrepo/repo.git"; new SpringApplicationBuilder(TestConfiguration.class).web(WebApplicationType.NONE) .properties(gitProperties(repoUrl, null, HTTPS_PROXY)).run(); HttpClient httpClient = getHttpClientForUrl(repoUrl); - this.expectedException.expectCause( - allOf(instanceOf(UnknownHostException.class), hasProperty("message", containsString("somehost")))); + this.expectedException.expectCause(allOf(instanceOf(UnknownHostException.class), + hasProperty("message", containsString(HTTPS_PROXY.getHost())))); makeRequest(httpClient, "http://somehost"); } @@ -182,13 +182,13 @@ public class ConfigurableHttpConnectionFactoryIntegrationTests { } @Test - public void httpProxy_notCalled() throws Exception { + public void httpProxy_called_for_https_request_when_no_httpsProxy_specified() throws Exception { String repoUrl = "https://myrepo/repo.git"; new SpringApplicationBuilder(TestConfiguration.class).web(WebApplicationType.NONE) .properties(gitProperties(repoUrl, HTTP_PROXY, null)).run(); HttpClient httpClient = getHttpClientForUrl(repoUrl); - this.expectedException.expectCause( - allOf(instanceOf(UnknownHostException.class), hasProperty("message", containsString("somehost")))); + this.expectedException.expectCause(allOf(instanceOf(UnknownHostException.class), + hasProperty("message", containsString(HTTP_PROXY.getHost())))); makeRequest(httpClient, "https://somehost"); } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlannerTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlannerTest.java new file mode 100644 index 00000000..0ac241b5 --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/proxy/SchemeBasedRoutePlannerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2018-2019 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 + * + * https://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.config.server.proxy; + +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SchemeBasedRoutePlannerTest { + + private static final ProxyHostProperties SECURED_PROXY_PROPERTIES = buildProxyProperties("http.host", 8080); + + private static final ProxyHostProperties UNSECURED_PROXY_PROPERTIES = buildProxyProperties("https.host", 8443); + + @Test + void determineProxy_should_return_https_proxy_when_target_scheme_name_is_https_and_https_proxy_provided() { + SchemeBasedRoutePlanner planner = new SchemeBasedRoutePlanner(SECURED_PROXY_PROPERTIES, UNSECURED_PROXY_PROPERTIES); + + final HttpHost result = planner.determineProxy(target("https"), anyRequest(), anyContext()); + + assertThat(result.getSchemeName()).isEqualTo("https"); + assertThat(result.getHostName()).isEqualTo(SECURED_PROXY_PROPERTIES.getHost()); + assertThat(result.getPort()).isEqualTo(SECURED_PROXY_PROPERTIES.getPort()); + } + + @Test + void determineProxy_should_return_https_proxy_when_target_scheme_name_is_http_and_no_http_proxy_specified() { + SchemeBasedRoutePlanner planner = new SchemeBasedRoutePlanner(SECURED_PROXY_PROPERTIES, null); + + final HttpHost result = planner.determineProxy(target("http"), anyRequest(), anyContext()); + + assertThat(result.getSchemeName()).isEqualTo("https"); + assertThat(result.getHostName()).isEqualTo(SECURED_PROXY_PROPERTIES.getHost()); + assertThat(result.getPort()).isEqualTo(SECURED_PROXY_PROPERTIES.getPort()); + } + + @Test + void determineProxy_should_return_http_proxy_when_target_scheme_name_is_http_and_http_proxy_provided() { + SchemeBasedRoutePlanner planner = new SchemeBasedRoutePlanner(SECURED_PROXY_PROPERTIES, UNSECURED_PROXY_PROPERTIES); + + final HttpHost result = planner.determineProxy(target("http"), anyRequest(), anyContext()); + + assertThat(result.getSchemeName()).isEqualTo("http"); + assertThat(result.getHostName()).isEqualTo(UNSECURED_PROXY_PROPERTIES.getHost()); + assertThat(result.getPort()).isEqualTo(UNSECURED_PROXY_PROPERTIES.getPort()); + } + + @Test + void determineProxy_should_return_http_proxy_when_target_scheme_name_is_https_and_https_proxy_provided() { + SchemeBasedRoutePlanner planner = new SchemeBasedRoutePlanner(null, UNSECURED_PROXY_PROPERTIES); + + final HttpHost result = planner.determineProxy(target("https"), anyRequest(), anyContext()); + + assertThat(result.getSchemeName()).isEqualTo("http"); + assertThat(result.getHostName()).isEqualTo(UNSECURED_PROXY_PROPERTIES.getHost()); + assertThat(result.getPort()).isEqualTo(UNSECURED_PROXY_PROPERTIES.getPort()); + } + + private HttpHost target(String scheme) { + HttpHost host = mock(HttpHost.class); + when(host.getSchemeName()).thenReturn(scheme); + return host; + } + + private HttpRequest anyRequest() { + return mock(HttpRequest.class); + } + + private HttpContext anyContext() { + return mock(HttpContext.class); + } + + private static ProxyHostProperties buildProxyProperties(String host, int port) { + ProxyHostProperties properties = new ProxyHostProperties(); + properties.setHost(host); + properties.setPort(port); + return properties; + } + +}