diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java index 9acd3b39..afd99b09 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java @@ -15,10 +15,19 @@ */ 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; @@ -59,7 +68,9 @@ 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 @@ -101,6 +112,75 @@ public class EnvironmentRepositoryConfiguration { return new EnvironmentWatch.Default(); } } + + @Configuration + @ConditionalOnClass(TransportConfigCallback.class) + static class JGitFactoryConfig { + @Bean + public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory( + ConfigurableEnvironment environment, ConfigServerProperties server, + Optional customTransportConfigCallback) { + return new MultipleJGitEnvironmentRepositoryFactory(environment, server, customTransportConfigCallback); + } + } + + @Configuration + @ConditionalOnClass(SVNException.class) + static class SvnFactoryConfig { + @Bean + public SvnEnvironmentRepositoryFactory svnEnvironmentRepositoryFactory(ConfigurableEnvironment environment, + ConfigServerProperties server) { + return new SvnEnvironmentRepositoryFactory(environment, server); + } + } + + @Configuration + static class VaultFactoryConfig { + @Bean + public VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory( + ObjectProvider request, EnvironmentWatch watch, + Optional skipSslValidationRestTemplate) { + return new VaultEnvironmentRepositoryFactory(request, watch, skipSslValidationRestTemplate); + } + } + + @Configuration + @ConditionalOnClass(JdbcTemplate.class) + static class JdbcCompositeConfig { + @Bean + @ConditionalOnBean(JdbcTemplate.class) + public JdbcEnvironmentRepositoryFactory jdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) { + return new JdbcEnvironmentRepositoryFactory(jdbc); + } + } + + @Configuration + static class NativeFactoryConfig { + @Bean + public NativeEnvironmentRepositoryFactory nativeEnvironmentRepositoryFactory(ConfigurableEnvironment environment, + ConfigServerProperties properties) { + return new NativeEnvironmentRepositoryFactory(environment, properties); + } + } + + @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 @@ -117,10 +197,8 @@ class DefaultRepositoryConfiguration { @Bean public MultipleJGitEnvironmentRepository defaultEnvironmentRepository( + MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory, MultipleJGitEnvironmentProperties environmentProperties) { - MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory = - new MultipleJGitEnvironmentRepositoryFactory(environment, server, - Optional.ofNullable(customTransportConfigCallback)); return gitEnvironmentRepositoryFactory.build(environmentProperties); } } @@ -129,19 +207,11 @@ class DefaultRepositoryConfiguration { @ConditionalOnMissingBean(EnvironmentRepository.class) @Profile("native") class NativeRepositoryConfiguration { - @Autowired - private ConfigurableEnvironment environment; - - @Autowired - private ConfigServerProperties configServerProperties; @Bean - public NativeEnvironmentRepository nativeEnvironmentRepository( + public NativeEnvironmentRepository nativeEnvironmentRepository(NativeEnvironmentRepositoryFactory factory, NativeEnvironmentProperties environmentProperties) { - NativeEnvironmentRepository repository = new NativeEnvironmentRepository(this.environment, - environmentProperties); - repository.setDefaultLabel(configServerProperties.getDefaultLabel()); - return repository; + return factory.build(environmentProperties); } } @@ -153,25 +223,22 @@ class GitRepositoryConfiguration extends DefaultRepositoryConfiguration { @Configuration @Profile("subversion") class SvnRepositoryConfiguration { - @Autowired - private ConfigurableEnvironment environment; - - @Autowired - private ConfigServerProperties server; @Bean - public SvnKitEnvironmentRepository svnKitEnvironmentRepository(SvnKitEnvironmentProperties environmentProperties) { - return new SvnEnvironmentRepositoryFactory(environment, server).build(environmentProperties); + public SvnKitEnvironmentRepository svnKitEnvironmentRepository(SvnKitEnvironmentProperties environmentProperties, + SvnEnvironmentRepositoryFactory factory) { + return factory.build(environmentProperties); } } @Configuration @Profile("vault") class VaultRepositoryConfiguration { + @Bean - public VaultEnvironmentRepository vaultEnvironmentRepository(ObjectProvider request, EnvironmentWatch watch, - VaultEnvironmentProperties environmentProperties) { - return new VaultEnvironmentRepositoryFactory(request, watch).build(environmentProperties); + public VaultEnvironmentRepository vaultEnvironmentRepository(VaultEnvironmentRepositoryFactory factory, + VaultEnvironmentProperties environmentProperties) { + return factory.build(environmentProperties); } } @@ -182,9 +249,9 @@ class JdbcRepositoryConfiguration { @Bean @ConditionalOnBean(JdbcTemplate.class) - public JdbcEnvironmentRepository jdbcEnvironmentRepository(JdbcTemplate jdbc, + public JdbcEnvironmentRepository jdbcEnvironmentRepository(JdbcEnvironmentRepositoryFactory factory, JdbcEnvironmentProperties environmentProperties) { - return new JdbcEnvironmentRepositoryFactory(jdbc).build(environmentProperties); + return factory.build(environmentProperties); } } @@ -192,47 +259,6 @@ class JdbcRepositoryConfiguration { @Profile("composite") class CompositeRepositoryConfiguration { - @Configuration - @ConditionalOnClass(TransportConfigCallback.class) - static class JGitCompositeConfig { - @Bean - public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory( - ConfigurableEnvironment environment, ConfigServerProperties server, - Optional customTransportConfigCallback) { - return new MultipleJGitEnvironmentRepositoryFactory(environment, server, customTransportConfigCallback); - } - } - - @Configuration - @ConditionalOnClass(SVNException.class) - static class SvnCompositeConfig { - @Bean - public SvnEnvironmentRepositoryFactory svnEnvironmentRepositoryFactory(ConfigurableEnvironment environment, - ConfigServerProperties server) { - return new SvnEnvironmentRepositoryFactory(environment, server); - } - } - - @Bean - public VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(ObjectProvider request, - EnvironmentWatch watch) { - return new VaultEnvironmentRepositoryFactory(request, watch); - } - - @Configuration - @ConditionalOnClass(JdbcTemplate.class) - static class JdbcCompositeConfig { - @Bean - public JdbcEnvironmentRepositoryFactory jdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) { - return new JdbcEnvironmentRepositoryFactory(jdbc); - } - } - - @Bean - public NativeEnvironmentRepositoryFactory nativeEnvironmentRepositoryFactory(ConfigurableEnvironment environment) { - return new NativeEnvironmentRepositoryFactory(environment); - } - @Bean public static CompositeEnvironmentBeanFactoryPostProcessor compositeEnvironmentRepositoryBeanFactoryPostProcessor( Environment environment) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryFactory.java index 1e8b64af..d8daa2b5 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepositoryFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.config.server.environment; +import org.springframework.cloud.config.server.config.ConfigServerProperties; import org.springframework.core.env.ConfigurableEnvironment; /** @@ -23,13 +24,17 @@ import org.springframework.core.env.ConfigurableEnvironment; public class NativeEnvironmentRepositoryFactory implements EnvironmentRepositoryFactory { private ConfigurableEnvironment environment; + private ConfigServerProperties properties; - public NativeEnvironmentRepositoryFactory(ConfigurableEnvironment environment) { + public NativeEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties properties) { this.environment = environment; + this.properties = properties; } @Override public NativeEnvironmentRepository build(NativeEnvironmentProperties environmentProperties) { - return new NativeEnvironmentRepository(environment, environmentProperties); + NativeEnvironmentRepository repository = new NativeEnvironmentRepository(environment, environmentProperties); + repository.setDefaultLabel(properties.getDefaultLabel()); + return repository; } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java index 4d17c7af..3ae680e1 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java @@ -36,6 +36,11 @@ public class VaultEnvironmentProperties implements EnvironmentRepositoryProperti private String defaultKey = "application"; /** Vault profile separator. Defaults to comma. */ private String profileSeparator = ","; + /** + * Flag to indicate that SSL certificate validation should be bypassed when communicating with a repository served + * over an HTTPS connection. + */ + private boolean skipSslValidation = false; private int order = Ordered.LOWEST_PRECEDENCE; public String getHost() { @@ -86,6 +91,14 @@ public class VaultEnvironmentProperties implements EnvironmentRepositoryProperti this.profileSeparator = profileSeparator; } + public boolean isSkipSslValidation() { + return skipSslValidation; + } + + public void setSkipSslValidation(boolean skipSslValidation) { + this.skipSslValidation = skipSslValidation; + } + public int getOrder() { return order; } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java index 9639a716..6128d5dd 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.config.server.environment; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.ObjectProvider; @@ -27,16 +28,21 @@ public class VaultEnvironmentRepositoryFactory implements EnvironmentRepositoryF VaultEnvironmentProperties> { private ObjectProvider request; private EnvironmentWatch watch; + private Optional skipSslValidationRestTemplate; - public VaultEnvironmentRepositoryFactory(ObjectProvider request, EnvironmentWatch watch) { + public VaultEnvironmentRepositoryFactory(ObjectProvider request, EnvironmentWatch watch, + Optional skipSslValidationRestTemplate) { this.request = request; this.watch = watch; + this.skipSslValidationRestTemplate = skipSslValidationRestTemplate; } @Override public VaultEnvironmentRepository build(VaultEnvironmentProperties environmentProperties) { - VaultEnvironmentRepository repository = new VaultEnvironmentRepository(request, watch, new RestTemplate(), - environmentProperties); - return repository; + if (environmentProperties.isSkipSslValidation() && skipSslValidationRestTemplate.isPresent()) { + return new VaultEnvironmentRepository(request, watch, skipSslValidationRestTemplate.get(), + environmentProperties); + } + return new VaultEnvironmentRepository(request, watch, new RestTemplate(), environmentProperties); } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryIntegrationTests.java new file mode 100644 index 00000000..3ea6ebe4 --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryIntegrationTests.java @@ -0,0 +1,136 @@ +/* + * 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.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; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +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; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * author Dylan Roberts + */ +@RunWith(SpringRunner.class) +@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"}) +public class VaultEnvironmentRepositoryIntegrationTests { + + @LocalServerPort + private String localServerPort; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void withSslValidation() { + VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory = + new VaultEnvironmentRepositoryFactory(withRequest(), new EnvironmentWatch.Default(), + Optional.of(skipSslValidationRestTemplate())); + VaultEnvironmentRepository vaultEnvironmentRepository = + vaultEnvironmentRepositoryFactory.build(withEnvironmentProperties(false)); + expectedException.expectCause(instanceOf(SSLHandshakeException.class)); + + vaultEnvironmentRepository.findOne("application", "profile", "label"); + } + + @Test + public void skipSslValidation() { + VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory = + new VaultEnvironmentRepositoryFactory(withRequest(), new EnvironmentWatch.Default(), + Optional.of(skipSslValidationRestTemplate())); + VaultEnvironmentRepository vaultEnvironmentRepository = + vaultEnvironmentRepositoryFactory.build(withEnvironmentProperties(true)); + + Environment actual = vaultEnvironmentRepository.findOne("application", "profile", "label"); + + 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)); + environmentProperties.setScheme("https"); + environmentProperties.setSkipSslValidation(skipSslValidation); + return environmentProperties; + } + + private ObjectProvider withRequest() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader("X-Config-Token")).thenReturn("configToken"); + ObjectProvider requestProvider = mock(ObjectProvider.class); + when(requestProvider.getIfAvailable()).thenReturn(request); + return requestProvider; + } + + @SpringBootConfiguration + @EnableAutoConfiguration + public static class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + } +} diff --git a/spring-cloud-config-server/src/test/resources/ssl-test.jks b/spring-cloud-config-server/src/test/resources/ssl-test.jks new file mode 100644 index 00000000..a2a3be65 Binary files /dev/null and b/spring-cloud-config-server/src/test/resources/ssl-test.jks differ