Enable http proxy support for Vault and Git (#1002)

gh-992
This commit is contained in:
Dylan Roberts
2018-05-24 13:30:44 -04:00
committed by Ryan Baxter
parent ab35034b98
commit 8e8f8c3ab4
19 changed files with 913 additions and 83 deletions

View File

@@ -48,6 +48,11 @@
<artifactId>org.eclipse.jgit.junit.http</artifactId>
<version>${jgit.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
<version>${jgit.version}</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>

View File

@@ -51,6 +51,10 @@
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>

View File

@@ -15,20 +15,12 @@
*/
package org.springframework.cloud.config.server.config;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.tmatesoft.svn.core.SVNException;
@@ -47,6 +39,9 @@ import org.springframework.cloud.config.server.environment.CompositeEnvironmentR
import org.springframework.cloud.config.server.environment.ConsulEnvironmentWatch;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.HttpClientConfigurableHttpConnectionFactory;
import org.springframework.cloud.config.server.environment.HttpClientVaultRestTemplateFactory;
import org.springframework.cloud.config.server.environment.ConfigurableHttpConnectionFactory;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentProperties;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepository;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepositoryFactory;
@@ -70,9 +65,7 @@ import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.client.RestTemplate;
/**
* @author Dave Syer
@@ -124,11 +117,24 @@ public class EnvironmentRepositoryConfiguration {
@Configuration
@ConditionalOnClass(TransportConfigCallback.class)
static class JGitFactoryConfig {
@Bean
public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory(
ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> jgitHttpConnectionFactory,
Optional<TransportConfigCallback> customTransportConfigCallback) {
return new MultipleJGitEnvironmentRepositoryFactory(environment, server, customTransportConfigCallback);
return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory,
customTransportConfigCallback);
}
}
@Configuration
@ConditionalOnClass({ HttpClient.class, TransportConfigCallback.class })
static class JGitHttpClientConfig {
@Bean
public ConfigurableHttpConnectionFactory httpClientConnectionFactory() {
return new HttpClientConfigurableHttpConnectionFactory();
}
}
@@ -144,17 +150,28 @@ public class EnvironmentRepositoryConfiguration {
@Configuration
static class VaultFactoryConfig {
@Bean
public VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
Optional<RestTemplate> skipSslValidationRestTemplate) {
return new VaultEnvironmentRepositoryFactory(request, watch, skipSslValidationRestTemplate);
Optional<VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory> vaultRestTemplateFactory) {
return new VaultEnvironmentRepositoryFactory(request, watch, vaultRestTemplateFactory);
}
}
@Configuration
@ConditionalOnClass(HttpClient.class)
static class VaultHttpClientConfig {
@Bean
public VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory vaultRestTemplateFactory() {
return new HttpClientVaultRestTemplateFactory();
}
}
@Configuration
@ConditionalOnClass(JdbcTemplate.class)
static class JdbcCompositeConfig {
static class JdbcFactoryConfig {
@Bean
@ConditionalOnBean(JdbcTemplate.class)
public JdbcEnvironmentRepositoryFactory jdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) {
@@ -171,24 +188,6 @@ public class EnvironmentRepositoryConfiguration {
}
}
@Bean
@ConditionalOnClass(HttpClient.class)
public RestTemplate skipSslValidationRestTemplate() {
try {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (certificate, authType) -> true)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new RuntimeException(e);
}
}
}
@Configuration
@@ -206,7 +205,7 @@ class DefaultRepositoryConfiguration {
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) {
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
@@ -245,7 +244,8 @@ class VaultRepositoryConfiguration {
@Bean
public VaultEnvironmentRepository vaultEnvironmentRepository(VaultEnvironmentRepositoryFactory factory,
VaultEnvironmentProperties environmentProperties) {
VaultEnvironmentProperties environmentProperties)
throws Exception {
return factory.build(environmentProperties);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2018 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.config.server.environment;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
/**
* @author Dylan Roberts
*/
public interface ConfigurableHttpConnectionFactory extends HttpConnectionFactory {
void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties) throws Exception;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2018 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.config.server.environment;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.apache.HttpClientConnection;
import org.springframework.cloud.config.server.support.HttpClientSupport;
/**
* @author Dylan Roberts
*/
public class HttpClientConfigurableHttpConnectionFactory implements ConfigurableHttpConnectionFactory {
private Map<String, HttpClientBuilder> httpClientsByUri = new HashMap<>();
@Override
public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties)
throws GeneralSecurityException {
addHttpClient(environmentProperties);
for (JGitEnvironmentProperties repo : environmentProperties.getRepos().values()) {
addHttpClient(repo);
}
}
@Override
public HttpConnection create(URL url) throws IOException {
return create(url, null);
}
@Override
public HttpConnection create(URL url, Proxy proxy) throws IOException {
return new HttpClientConnection(url.toString(), proxy, lookupHttpClientBuilder(url).build());
}
private void addHttpClient(JGitEnvironmentProperties properties) throws GeneralSecurityException {
if (properties.getUri().startsWith("http")) {
httpClientsByUri.put(properties.getUri(), HttpClientSupport.builder(properties));
}
}
private HttpClientBuilder lookupHttpClientBuilder(URL url) throws MalformedURLException {
String spec = url.toString();
HttpClientBuilder builder = httpClientsByUri.get(spec);
while (builder == null) {
spec = spec.substring(0, spec.lastIndexOf(File.separator));
builder = httpClientsByUri.get(spec);
}
return builder;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2018 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.config.server.environment;
import java.security.GeneralSecurityException;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.cloud.config.server.support.HttpClientSupport;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author Dylan Roberts
*/
public class HttpClientVaultRestTemplateFactory implements VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory {
@Override
public RestTemplate build(VaultEnvironmentProperties environmentProperties) throws GeneralSecurityException {
HttpClient httpClient = HttpClientSupport.builder(environmentProperties).build();
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}

View File

@@ -15,15 +15,20 @@
*/
package org.springframework.cloud.config.server.environment;
import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.Pattern;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.support.AbstractScmAccessorProperties;
import org.springframework.cloud.config.server.support.HttpEnvironmentRepositoryProperties;
/**
* @author Dylan Roberts
* @author Gareth Clay
*/
public class JGitEnvironmentProperties extends AbstractScmAccessorProperties {
public class JGitEnvironmentProperties extends AbstractScmAccessorProperties
implements HttpEnvironmentRepositoryProperties {
private static final String DEFAULT_LABEL = "master";
/** Flag to indicate that the repository should be cloned on startup (not on demand). Generally leads to slower startup but faster first query. */
@@ -85,6 +90,11 @@ public class JGitEnvironmentProperties extends AbstractScmAccessorProperties {
*/
private boolean strictHostKeyChecking = true;
/**
* HTTP proxy configuration.
*/
private Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy = new HashMap<>();
public JGitEnvironmentProperties() {
super();
setDefaultLabel(DEFAULT_LABEL);
@@ -188,6 +198,7 @@ public class JGitEnvironmentProperties extends AbstractScmAccessorProperties {
this.strictHostKeyChecking = strictHostKeyChecking;
}
@Override
public boolean isSkipSslValidation() {
return skipSslValidation;
}
@@ -195,4 +206,13 @@ public class JGitEnvironmentProperties extends AbstractScmAccessorProperties {
public void setSkipSslValidation(boolean skipSslValidation) {
this.skipSslValidation = skipSslValidation;
}
@Override
public Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> getProxy() {
return proxy;
}
public void setProxy(Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy) {
this.proxy = proxy;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.config.server.environment;
import java.util.Optional;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.transport.HttpTransport;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.config.server.ssh.FileBasedSshTransportConfigCallback;
@@ -31,17 +32,33 @@ public class MultipleJGitEnvironmentRepositoryFactory implements EnvironmentRepo
MultipleJGitEnvironmentProperties> {
private ConfigurableEnvironment environment;
private ConfigServerProperties server;
private Optional<ConfigurableHttpConnectionFactory> connectionFactory;
private Optional<TransportConfigCallback> customTransportConfigCallback;
public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<TransportConfigCallback> customTransportConfigCallback) {
@Deprecated
public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<TransportConfigCallback> customTransportConfigCallback) {
this(environment, server, Optional.empty(), customTransportConfigCallback);
}
public MultipleJGitEnvironmentRepositoryFactory(
ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> connectionFactory,
Optional<TransportConfigCallback> customTransportConfigCallback) {
this.environment = environment;
this.server = server;
this.connectionFactory = connectionFactory;
this.customTransportConfigCallback = customTransportConfigCallback;
}
@Override
public MultipleJGitEnvironmentRepository build(MultipleJGitEnvironmentProperties environmentProperties) {
public MultipleJGitEnvironmentRepository build(MultipleJGitEnvironmentProperties environmentProperties)
throws Exception {
if (connectionFactory.isPresent()) {
HttpTransport.setConnectionFactory(connectionFactory.get());
connectionFactory.get().addConfiguration(environmentProperties);
}
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(environment,
environmentProperties);
repository.setTransportConfigCallback(customTransportConfigCallback
@@ -52,10 +69,11 @@ public class MultipleJGitEnvironmentRepositoryFactory implements EnvironmentRepo
return repository;
}
private TransportConfigCallback buildTransportConfigCallback(final MultipleJGitEnvironmentProperties gitEnvironmentProperties) {
private TransportConfigCallback buildTransportConfigCallback(MultipleJGitEnvironmentProperties gitEnvironmentProperties) {
if (gitEnvironmentProperties.isIgnoreLocalSshSettings()) {
return new PropertiesBasedSshTransportConfigCallback(gitEnvironmentProperties);
}
return new FileBasedSshTransportConfigCallback(gitEnvironmentProperties);
}
}

View File

@@ -15,15 +15,19 @@
*/
package org.springframework.cloud.config.server.environment;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.support.HttpEnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Dylan Roberts
*/
@ConfigurationProperties("spring.cloud.config.server.vault")
public class VaultEnvironmentProperties implements EnvironmentRepositoryProperties {
public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProperties {
/** Vault host. Defaults to 127.0.0.1. */
private String host = "127.0.0.1";
/** Vault port. Defaults to 8200. */
@@ -41,6 +45,12 @@ public class VaultEnvironmentProperties implements EnvironmentRepositoryProperti
* over an HTTPS connection.
*/
private boolean skipSslValidation = false;
/**
* HTTP proxy configuration.
*/
private Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy = new HashMap<>();
private int order = Ordered.LOWEST_PRECEDENCE;
public String getHost() {
@@ -91,6 +101,7 @@ public class VaultEnvironmentProperties implements EnvironmentRepositoryProperti
this.profileSeparator = profileSeparator;
}
@Override
public boolean isSkipSslValidation() {
return skipSslValidation;
}
@@ -99,6 +110,15 @@ public class VaultEnvironmentProperties implements EnvironmentRepositoryProperti
this.skipSslValidation = skipSslValidation;
}
@Override
public Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> getProxy() {
return proxy;
}
public void setProxy(Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy) {
this.proxy = proxy;
}
public int getOrder() {
return order;
}

View File

@@ -28,21 +28,26 @@ public class VaultEnvironmentRepositoryFactory implements EnvironmentRepositoryF
VaultEnvironmentProperties> {
private ObjectProvider<HttpServletRequest> request;
private EnvironmentWatch watch;
private Optional<RestTemplate> skipSslValidationRestTemplate;
private Optional<VaultRestTemplateFactory> vaultRestTemplateFactory;
public VaultEnvironmentRepositoryFactory(ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
Optional<RestTemplate> skipSslValidationRestTemplate) {
Optional<VaultRestTemplateFactory> vaultRestTemplateFactory) {
this.request = request;
this.watch = watch;
this.skipSslValidationRestTemplate = skipSslValidationRestTemplate;
this.vaultRestTemplateFactory = vaultRestTemplateFactory;
}
@Override
public VaultEnvironmentRepository build(VaultEnvironmentProperties environmentProperties) {
if (environmentProperties.isSkipSslValidation() && skipSslValidationRestTemplate.isPresent()) {
return new VaultEnvironmentRepository(request, watch, skipSslValidationRestTemplate.get(),
environmentProperties);
public VaultEnvironmentRepository build(VaultEnvironmentProperties environmentProperties) throws Exception {
if (vaultRestTemplateFactory.isPresent()) {
RestTemplate restTemplate = vaultRestTemplateFactory.get().build(environmentProperties);
return new VaultEnvironmentRepository(request, watch, restTemplate, environmentProperties);
}
return new VaultEnvironmentRepository(request, watch, new RestTemplate(), environmentProperties);
}
public interface VaultRestTemplateFactory {
RestTemplate build(VaultEnvironmentProperties environmentProperties) throws Exception;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2018 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.config.server.proxy;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
/**
* @author Dylan Roberts
*/
public class ProxyHostCredentialsProvider extends BasicCredentialsProvider {
public ProxyHostCredentialsProvider(ProxyHostProperties... proxyHostProperties) {
for (ProxyHostProperties proxy : proxyHostProperties) {
if (proxy != null && proxy.getUsername() != null && proxy.getPassword() != null) {
AuthScope authscope = new AuthScope(proxy.getHost(), proxy.getPort());
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(proxy.getUsername(),
proxy.getPassword());
setCredentials(authscope, credentials);
}
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2018 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.config.server.proxy;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public class ProxyHostProperties {
private String host;
private int port;
private String nonProxyHosts;
private String username;
private String password;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getNonProxyHosts() {
return nonProxyHosts;
}
public void setNonProxyHosts(String nonProxyHosts) {
this.nonProxyHosts = nonProxyHosts;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public enum ProxyForScheme {
HTTP,
HTTPS;
@JsonCreator
public static ProxyForScheme forLowerCaseName(String lowerCaseName) {
return ProxyForScheme.valueOf(lowerCaseName.toUpperCase());
}
@JsonValue
public String lowercaseName() {
return this.name().toLowerCase();
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2018 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.config.server.proxy;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.impl.conn.DefaultRoutePlanner;
import org.apache.http.protocol.HttpContext;
/**
* @author Dylan Roberts
*/
public class SchemeBasedRoutePlanner extends DefaultRoutePlanner {
private final ProxyHostProperties httpsProxy;
private final ProxyHostProperties httpProxy;
public SchemeBasedRoutePlanner(ProxyHostProperties httpsProxy, ProxyHostProperties httpProxy) {
super(null);
this.httpsProxy = httpsProxy;
this.httpProxy = httpProxy;
}
@Override
protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) {
return "https".equals(target.getSchemeName()) ?
determineProxy(httpsProxy) :
determineProxy(httpProxy);
}
private HttpHost determineProxy(ProxyHostProperties properties) {
if (properties == null) {
return null;
}
return new HttpHost(properties.getHost(), properties.getPort());
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2018 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.config.server.support;
import java.security.GeneralSecurityException;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.cloud.config.server.proxy.ProxyHostCredentialsProvider;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.proxy.SchemeBasedRoutePlanner;
import org.springframework.util.CollectionUtils;
/**
* @author Dylan Roberts
*/
public class HttpClientSupport {
public static HttpClientBuilder builder(HttpEnvironmentRepositoryProperties environmentProperties)
throws GeneralSecurityException {
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (environmentProperties.isSkipSslValidation()) {
sslContextBuilder.loadTrustMaterial(null, (certificate, authType) -> true);
httpClientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier());
}
if (!CollectionUtils.isEmpty(environmentProperties.getProxy())) {
ProxyHostProperties httpsProxy = environmentProperties.getProxy().get(ProxyHostProperties.ProxyForScheme.HTTPS);
ProxyHostProperties httpProxy = environmentProperties.getProxy().get(ProxyHostProperties.ProxyForScheme.HTTP);
httpClientBuilder.setRoutePlanner(new SchemeBasedRoutePlanner(httpsProxy, httpProxy));
httpClientBuilder.setDefaultCredentialsProvider(new ProxyHostCredentialsProvider(httpProxy, httpsProxy));
}
return httpClientBuilder.setSSLContext(sslContextBuilder.build());
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2018 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.config.server.support;
import java.util.Map;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
/**
* @author Dylan Roberts
*/
public interface HttpEnvironmentRepositoryProperties extends EnvironmentRepositoryProperties {
Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> getProxy();
boolean isSkipSslValidation();
}

View File

@@ -45,6 +45,26 @@ public class CompositeClasspathTests {
}
}
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("httpclient-*.jar")
public static class HttpClientTests {
@Test
public void contextLoads() {
new WebApplicationContextRunner()
.withUserConfiguration(ConfigServerApplication.class)
.withPropertyValues("spring.profiles.active:test,composite",
"spring.config.name:compositeconfigserver",
"spring.cloud.config.server.composite[0].uri:file:./target/repos/config-repo",
"spring.cloud.config.server.composite[0].type:git",
"spring.cloud.config.server.composite[1].uri:file:///./target/repos/svn-config-repo",
"spring.cloud.config.server.composite[1].type:svn")
.run(context -> {
CompositeUtils.getCompositeTypeList(context.getEnvironment());
});
}
}
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("svnkit-*.jar")
public static class SvnTests {

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2018 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.config.server.config;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cloud.config.server.environment.HttpClientVaultRestTemplateFactory;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.instanceOf;
/**
* @author Dylan Roberts
*/
public class HttpClientVaultRestTemplateFactoryTest {
private static final ProxyHostProperties AUTHENTICATED_HTTP_PROXY = new ProxyHostProperties();
static {
AUTHENTICATED_HTTP_PROXY.setHost("http://authenticated.http.proxy");
AUTHENTICATED_HTTP_PROXY.setPort(8080);
AUTHENTICATED_HTTP_PROXY.setUsername("username");
AUTHENTICATED_HTTP_PROXY.setPassword("password");
}
private static final ProxyHostProperties AUTHENTICATED_HTTPS_PROXY = new ProxyHostProperties();
static {
AUTHENTICATED_HTTPS_PROXY.setHost("http://authenticated.https.proxy");
AUTHENTICATED_HTTPS_PROXY.setPort(8081);
AUTHENTICATED_HTTPS_PROXY.setUsername("username2");
AUTHENTICATED_HTTPS_PROXY.setPassword("password2");
}
private static final ProxyHostProperties HTTP_PROXY = new ProxyHostProperties();
static {
HTTP_PROXY.setHost("http://http.proxy");
HTTP_PROXY.setPort(8080);
}
private static final ProxyHostProperties HTTPS_PROXY = new ProxyHostProperties();
static {
HTTPS_PROXY.setHost("http://https.proxy");
HTTPS_PROXY.setPort(8081);
}
private HttpClientVaultRestTemplateFactory factory;
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void setUp() {
factory = new HttpClientVaultRestTemplateFactory();
}
@Test
public void authenticatedHttpsProxy() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(null, AUTHENTICATED_HTTPS_PROXY);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(AUTHENTICATED_HTTPS_PROXY.getHost()))));
restTemplate.getForObject("https://somehost", String.class);
}
@Test
public void httpsProxy() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(null, HTTPS_PROXY);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(HTTPS_PROXY.getHost()))));
restTemplate.getForObject("https://somehost", String.class);
}
@Test
public void httpsProxy_notCalled() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(null, HTTPS_PROXY);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString("somehost"))));
restTemplate.getForObject("http://somehost", String.class);
}
@Test
public void authenticatedHttpProxy() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(AUTHENTICATED_HTTP_PROXY, null);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(AUTHENTICATED_HTTP_PROXY.getHost()))));
restTemplate.getForObject("http://somehost", String.class);
}
@Test
public void httpProxy() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(HTTP_PROXY, null);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(HTTP_PROXY.getHost()))));
restTemplate.getForObject("http://somehost", String.class);
}
@Test
public void httpProxy_notCalled() throws Exception {
VaultEnvironmentProperties properties = getVaultEnvironmentProperties(HTTP_PROXY, null);
RestTemplate restTemplate = factory.build(properties);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString("somehost"))));
restTemplate.getForObject("https://somehost", String.class);
}
private VaultEnvironmentProperties getVaultEnvironmentProperties(ProxyHostProperties httpProxy, ProxyHostProperties httpsProxy) {
Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxyMap = new HashMap<>();
proxyMap.put(ProxyHostProperties.ProxyForScheme.HTTP, httpProxy);
proxyMap.put(ProxyHostProperties.ProxyForScheme.HTTPS, httpsProxy);
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setProxy(proxyMap);
return properties;
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2018 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.config.server.environment;
import java.io.IOException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.http.client.HttpClient;
import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnection;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.instanceOf;
/**
* @author Dylan Roberts
*/
public class JGitEnvironmentRepositoryHttpProxyTests {
private static final ProxyHostProperties AUTHENTICATED_HTTP_PROXY = new ProxyHostProperties();
static {
AUTHENTICATED_HTTP_PROXY.setHost("http://authenticated.http.proxy");
AUTHENTICATED_HTTP_PROXY.setPort(8080);
AUTHENTICATED_HTTP_PROXY.setUsername("username");
AUTHENTICATED_HTTP_PROXY.setPassword("password");
}
private static final ProxyHostProperties AUTHENTICATED_HTTPS_PROXY = new ProxyHostProperties();
static {
AUTHENTICATED_HTTPS_PROXY.setHost("http://authenticated.https.proxy");
AUTHENTICATED_HTTPS_PROXY.setPort(8081);
AUTHENTICATED_HTTPS_PROXY.setUsername("username2");
AUTHENTICATED_HTTPS_PROXY.setPassword("password2");
}
private static final ProxyHostProperties HTTP_PROXY = new ProxyHostProperties();
static {
HTTP_PROXY.setHost("http://http.proxy");
HTTP_PROXY.setPort(8080);
}
private static final ProxyHostProperties HTTPS_PROXY = new ProxyHostProperties();
static {
HTTPS_PROXY.setHost("http://https.proxy");
HTTPS_PROXY.setPort(8081);
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void authenticatedHttpsProxy() throws Exception {
String repoUrl = "https://myrepo/repo.git";
new SpringApplicationBuilder(TestConfiguration.class)
.web(WebApplicationType.NONE)
.properties(gitProperties(repoUrl, null, AUTHENTICATED_HTTPS_PROXY))
.run();
HttpClient httpClient = getHttpClientForUrl(repoUrl);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(AUTHENTICATED_HTTPS_PROXY.getHost()))));
makeRequest(httpClient, "https://somehost");
}
@Test
public void httpsProxy() 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);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(HTTPS_PROXY.getHost()))));
makeRequest(httpClient, "https://somehost");
}
@Test
public void httpsProxy_notCalled() 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);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString("somehost"))));
makeRequest(httpClient, "http://somehost");
}
@Test
public void authenticatedHttpProxy() throws Exception {
String repoUrl = "https://myrepo/repo.git";
new SpringApplicationBuilder(TestConfiguration.class)
.web(WebApplicationType.NONE)
.properties(gitProperties(repoUrl, AUTHENTICATED_HTTP_PROXY, null))
.run();
HttpClient httpClient = getHttpClientForUrl(repoUrl);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(AUTHENTICATED_HTTP_PROXY.getHost()))));
makeRequest(httpClient, "http://somehost");
}
@Test
public void httpProxy() 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);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString(HTTP_PROXY.getHost()))));
makeRequest(httpClient, "http://somehost");
}
@Test
public void httpProxy_notCalled() 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);
expectedException.expectCause(allOf(
instanceOf(UnknownHostException.class),
hasProperty("message", containsString("somehost"))));
makeRequest(httpClient, "https://somehost");
}
private String[] gitProperties(String repoUrl, ProxyHostProperties httpProxy, ProxyHostProperties httpsProxy) {
List<String> result = new ArrayList<>();
result.add("spring.cloud.config.server.git.uri=" + repoUrl);
if (httpProxy != null) {
result.addAll(Arrays.asList(
"spring.cloud.config.server.git.proxy.http.host=" + httpProxy.getHost(),
"spring.cloud.config.server.git.proxy.http.port=" + httpProxy.getPort()
));
if (httpProxy.getUsername() != null && httpProxy.getPassword() != null) {
result.addAll(Arrays.asList(
"spring.cloud.config.server.git.proxy.http.username=" + httpProxy.getUsername(),
"spring.cloud.config.server.git.proxy.http.password=" + httpProxy.getPassword()
));
}
}
if (httpsProxy != null) {
result.addAll(Arrays.asList(
"spring.cloud.config.server.git.proxy.https.host=" + httpsProxy.getHost(),
"spring.cloud.config.server.git.proxy.https.port=" + httpsProxy.getPort()
));
if (httpsProxy.getUsername() != null && httpsProxy.getPassword() != null) {
result.addAll(Arrays.asList(
"spring.cloud.config.server.git.proxy.https.username=" + httpsProxy.getUsername(),
"spring.cloud.config.server.git.proxy.https.password=" + httpsProxy.getPassword()
));
}
}
return result.toArray(new String[0]);
}
private void makeRequest(HttpClient httpClient, String url) {
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
restTemplate.getForObject(url, String.class);
}
private HttpClient getHttpClientForUrl(String repoUrl) throws IOException {
HttpConnectionFactory connectionFactory = HttpTransport.getConnectionFactory();
HttpConnection httpConnection = connectionFactory.create(new URL(repoUrl));
assertThat(httpConnection).isInstanceOf(HttpClientConnection.class);
return (HttpClient) ReflectionTestUtils.getField(httpConnection, "client");
}
@Configuration
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ PropertyPlaceholderAutoConfiguration.class, EnvironmentRepositoryConfiguration.class })
protected static class TestConfiguration {
}
}

View File

@@ -15,18 +15,10 @@
*/
package org.springframework.cloud.config.server.environment;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -39,9 +31,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf;
@@ -55,10 +45,10 @@ import static org.mockito.Mockito.when;
@SpringBootTest(classes = VaultEnvironmentRepositoryIntegrationTests.TestApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"server.ssl.key-store=classpath:ssl-test.jks",
"server.ssl.key-store-password=password",
"server.ssl.key-password=password",
"server.key-alias=ssl-test"})
"server.ssl.key-store=classpath:ssl-test.jks",
"server.ssl.key-store-password=password",
"server.ssl.key-password=password",
"server.key-alias=ssl-test"})
public class VaultEnvironmentRepositoryIntegrationTests {
@LocalServerPort
@@ -68,10 +58,10 @@ public class VaultEnvironmentRepositoryIntegrationTests {
public ExpectedException expectedException = ExpectedException.none();
@Test
public void withSslValidation() {
public void withSslValidation() throws Exception {
VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory =
new VaultEnvironmentRepositoryFactory(withRequest(), new EnvironmentWatch.Default(),
Optional.of(skipSslValidationRestTemplate()));
Optional.of(new HttpClientVaultRestTemplateFactory()));
VaultEnvironmentRepository vaultEnvironmentRepository =
vaultEnvironmentRepositoryFactory.build(withEnvironmentProperties(false));
expectedException.expectCause(instanceOf(SSLHandshakeException.class));
@@ -80,10 +70,10 @@ public class VaultEnvironmentRepositoryIntegrationTests {
}
@Test
public void skipSslValidation() {
public void skipSslValidation() throws Exception {
VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory =
new VaultEnvironmentRepositoryFactory(withRequest(), new EnvironmentWatch.Default(),
Optional.of(skipSslValidationRestTemplate()));
Optional.of(new HttpClientVaultRestTemplateFactory()));
VaultEnvironmentRepository vaultEnvironmentRepository =
vaultEnvironmentRepositoryFactory.build(withEnvironmentProperties(true));
@@ -92,23 +82,6 @@ public class VaultEnvironmentRepositoryIntegrationTests {
assertThat(actual).isNotNull();
}
private RestTemplate skipSslValidationRestTemplate() {
try {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (certificate, authType) -> true)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new RuntimeException(e);
}
}
private VaultEnvironmentProperties withEnvironmentProperties(boolean skipSslValidation) {
VaultEnvironmentProperties environmentProperties = new VaultEnvironmentProperties();
environmentProperties.setPort(Integer.decode(localServerPort));