Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,28 @@ spring:

----

If config server requires client side TLS certificate, you can configure client side TLS certificate and trust store via properties, as shown in following example:

.bootstrap.yml
[source,yaml]
----
spring:
cloud:
config:
uri: https://myconfig.myconfig.com
tls:
enabled: true
key-store: <path-of-key-store>
key-store-type: PKCS12
key-store-password: <key-store-password>
key-password: <key-password>
trust-store: <path-of-trust-store>
trust-store-type: PKCS12
trust-store-password: <trust-store-password>
----

The `spring.cloud.config.tls.enabled` needs to be true to enable config client side TLS. When `spring.cloud.config.tls.trust-store` is omitted, a JVM default trust store is used. The default value for `spring.cloud.config.tls.key-store-type` and `spring.cloud.config.tls.trust-store-type` is PKCS12. When password properties are omitted, empty password is assumed.

If you use another form of security, you might need to <<custom-rest-template,provide a `RestTemplate`>> to the `ConfigServicePropertySourceLocator` (for example, by grabbing it in the bootstrap context and injecting it).

==== Health Indicator
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<spring-cloud-commons.version>2.2.5.BUILD-SNAPSHOT</spring-cloud-commons.version>
<aws-java-sdk.version>1.11.52</aws-java-sdk.version>
<google-api-services-iam.version>v1-rev20191010-1.30.3</google-api-services-iam.version>
<bouncycastle.version>1.64</bouncycastle.version>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
<maven-checkstyle-plugin.failsOnViolation>true
</maven-checkstyle-plugin.failsOnViolation>
Expand All @@ -43,6 +44,7 @@
<module>spring-cloud-config-monitor</module>
<module>spring-cloud-config-sample</module>
<module>spring-cloud-starter-config</module>
<module>spring-cloud-config-client-tls-tests</module>
<module>docs</module>
</modules>
<dependencyManagement>
Expand Down Expand Up @@ -87,6 +89,11 @@
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.15.0</version>
</dependency>
<dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanjbaxter move to test module

<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
Expand Down
107 changes: 107 additions & 0 deletions spring-cloud-config-client-tls-tests/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-config-client-tls-tests</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Config Client TLS Tests</name>

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>2.2.5.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

<url>https://spring.io</url>
<description>
<![CDATA[
This project is a Spring configuration client.
]]>
</description>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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.client;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.SocketUtils;

public class AppRunner implements AutoCloseable {

private Class<?> appClass;

private Map<String, String> props;

private ConfigurableApplicationContext app;

public AppRunner(Class<?> appClass) {
this.appClass = appClass;
props = new LinkedHashMap<>();
}

public void property(String key, String value) {
props.put(key, value);
}

public void start() {
if (app == null) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(appClass);
builder.properties("spring.jmx.enabled=false");
builder.properties(String.format("server.port=%d", availabeTcpPort()));
builder.properties(props());

app = builder.build().run();
}
}

private int availabeTcpPort() {
return SocketUtils.findAvailableTcpPort();
}

private String[] props() {
List<String> result = new ArrayList<>();

for (String key : props.keySet()) {
String value = props.get(key);
result.add(String.format("%s=%s", key, value));
}

return result.toArray(new String[0]);
}

public void stop() {
if (app != null) {
app.stop();
app = null;
}
}

public ConfigurableApplicationContext app() {
return app;
}

public String getProperty(String key) {
return app.getEnvironment().getProperty(key);
}

public <T> T getBean(Class<T> type) {
return app.getBean(type);
}

public ApplicationContext parent() {
return app.getParent();
}

public <T> Map<String, T> getParentBeans(Class<T> type) {
return parent().getBeansOfType(type);
}

public int port() {
if (app == null) {
throw new RuntimeException("App is not running.");
}
return app.getEnvironment().getProperty("server.port", Integer.class, -1);
}

public String root() {
if (app == null) {
throw new RuntimeException("App is not running.");
}

String protocol = tlsEnabled() ? "https" : "http";
return String.format("%s://localhost:%d/", protocol, port());
}

private boolean tlsEnabled() {
return app.getEnvironment().getProperty("server.ssl.enabled", Boolean.class,
false);
}

@Override
public void close() {
stop();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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.client;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.KeyStore;

import org.junit.BeforeClass;

public class BaseCertTest {

protected static final String KEY_STORE_PASSWORD = "test-key-store-password";

protected static final String KEY_PASSWORD = "test-key-password";

protected static final String WRONG_PASSWORD = "test-wrong-password";

protected static File caCert;

protected static File wrongCaCert;

protected static File serverCert;

protected static File clientCert;

protected static File wrongClientCert;

@BeforeClass
public static void createCertificates() throws Exception {
KeyTool tool = new KeyTool();

KeyAndCert ca = tool.createCA("MyCA");
KeyAndCert server = ca.sign("server");
KeyAndCert client = ca.sign("client");

caCert = saveCert(ca);
serverCert = saveKeyAndCert(server);
clientCert = saveKeyAndCert(client);

KeyAndCert wrongCa = tool.createCA("WrongCA");
KeyAndCert wrongClient = wrongCa.sign("client");

wrongCaCert = saveCert(wrongCa);
wrongClientCert = saveKeyAndCert(wrongClient);

System.setProperty("javax.net.ssl.trustStore", caCert.getAbsolutePath());
System.setProperty("javax.net.ssl.trustStorePassword", KEY_STORE_PASSWORD);
}

private static File saveKeyAndCert(KeyAndCert keyCert) throws Exception {
return saveKeyStore(keyCert.subject(),
() -> keyCert.storeKeyAndCert(KEY_PASSWORD));
}

private static File saveCert(KeyAndCert keyCert) throws Exception {
return saveKeyStore(keyCert.subject(), () -> keyCert.storeCert());
}

private static File saveKeyStore(String prefix, KeyStoreSupplier func)
throws Exception {
File result = File.createTempFile(prefix, ".p12");
result.deleteOnExit();

try (OutputStream output = new FileOutputStream(result)) {
KeyStore store = func.createKeyStore();
store.store(output, KEY_STORE_PASSWORD.toCharArray());
}
return result;
}

interface KeyStoreSupplier {

KeyStore createKeyStore() throws Exception;

}

}
Loading