Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,21 @@ Disable Property: `org.springframework.cloud.bindings.boot.hana.enable`
Type: `config`
Disable Property: `org.springframework.cloud.bindings.boot.config.enable`

| Property | Value |
| -------------------------------------------------- | -------------------- |
| `spring.cloud.config.uri` | `{uri}` |
| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` |
| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` |
| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` |
| Property | Value |
|------------------------------------------------------|--------------------------------------------------------|
| `spring.cloud.config.uri` | `{uri}` |
| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` |
| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` |
| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` |
| `spring.cloud.config.tls.enabled` | `true` when `{tls.crt}` and `{tls.key}` are set |
| `spring.cloud.config.tls.key-store` | derived from `{tls.crt}` and `{tls.key}` |
| `spring.cloud.config.tls.key-store-type` | `"PKCS12"` when `{tls.crt}` and `{tls.key}` are set |
| `spring.cloud.config.tls.key-store-password` | random string when `{tls.crt}` and `{tls.key}` are set |
| `spring.cloud.config.tls.key-alias` | `"config"` when `{tls.crt}` and `{tls.key}` are set |
| `spring.cloud.config.tls.key-password` | `""` when `{tls.crt}` and `{tls.key}` are set |
| `spring.cloud.config.tls.trust-store` | derived from `{ca.crt}` when it is set |
| `spring.cloud.config.tls.trust-store-type` | `"PKCS12"` when `{ca.crt}` is set |
| `spring.cloud.config.tls.trust-store-password` | random string when `{ca.crt}` is set |

## SCS Eureka

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@

import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.cloud.bindings.boot.pem.PemSslStoreHelper;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Map;
import java.util.Random;

import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;

Expand All @@ -40,12 +50,63 @@ public void process(Environment environment, Bindings bindings, Map<String, Obje
}

bindings.filterBindings(TYPE).forEach(binding -> {
MapMapper map = new MapMapper(binding.getSecret(), properties);
Map<String, String> secret = binding.getSecret();
MapMapper map = new MapMapper(secret, properties);
map.from("uri").to("spring.cloud.config.uri");
map.from("client-id").to("spring.cloud.config.client.oauth2.clientId");
map.from("client-secret").to("spring.cloud.config.client.oauth2.clientSecret");
map.from("access-token-uri").to("spring.cloud.config.client.oauth2.accessTokenUri");

// When tls.crt and tls.key are set, enable mTLS for config client.
String clientKey = secret.get("tls.key");
String clientCert = secret.get("tls.crt");
if (StringUtils.hasText(clientCert) != StringUtils.hasText(clientKey)) {
throw new IllegalArgumentException("binding secret error: tls.key and tls.crt must both be set if either is set");
}

if (clientKey != null && !clientKey.isEmpty()) {
String generatedPassword = new Random().ints(97 /* letter a */, 122 /* letter z */ + 1)
.limit(10)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();

// Create a keystore
String keyFilePath = createStore("client-keystore", generatedPassword, clientCert, clientKey, "config");

properties.put("spring.cloud.config.tls.enabled", true);
properties.put("spring.cloud.config.tls.key-alias", "config");
properties.put("spring.cloud.config.tls.key-store", "file:" + keyFilePath);
properties.put("spring.cloud.config.tls.key-store-type", "PKCS12");
properties.put("spring.cloud.config.tls.key-store-password", generatedPassword);
properties.put("spring.cloud.config.tls.key-password", "");

String caCert = secret.get("ca.crt");
if (caCert != null && !caCert.isEmpty()) {
// Create a truststore from the CA cert
String trustFilePath = createStore("client-truststore", generatedPassword, caCert, null, "ca");
properties.put("spring.cloud.config.tls.trust-store", "file:" + trustFilePath);
properties.put("spring.cloud.config.tls.trust-store-type", "PKCS12");
properties.put("spring.cloud.config.tls.trust-store-password", generatedPassword);
}
}
});
}

private static String createStore(String name, String password, String certificate, String privateKey, String keyAlias) {
String path = Paths.get(System.getProperty("java.io.tmpdir"), name + ".p12").toString();
KeyStore store = PemSslStoreHelper.createKeyStore("key", "PKCS12", certificate, privateKey, keyAlias);

try (FileOutputStream fos = new FileOutputStream(path)) {
store.store(fos, password.toCharArray());
} catch (KeyStoreException e) {
throw new IllegalStateException("Unable to write " + name, e);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cryptographic algorithm not available", e);
} catch (CertificateException e) {
throw new IllegalStateException("Unable to process certificate", e);
} catch (IOException e) {
throw new IllegalStateException("Unable to create " + name, e);
}
return path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@

package org.springframework.cloud.bindings.boot;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.cloud.bindings.FluentMap;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.env.MockEnvironment;

import java.io.File;
import java.nio.file.Paths;
import java.util.HashMap;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.cloud.bindings.boot.ConfigServerBindingsPropertiesProcessor.TYPE;

@DisplayName("Config Server BindingsPropertiesProcessor")
final class ConfigServerBindingsPropertiesProcessorTest {

private final Bindings bindings = new Bindings(
private Bindings bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, TYPE)
Expand All @@ -47,9 +52,20 @@ final class ConfigServerBindingsPropertiesProcessorTest {

private final HashMap<String, Object> properties = new HashMap<>();

private String cert;
private String key;

@BeforeEach
void fetchCerts() {
assertDoesNotThrow(() -> {
this.cert = TestHelper.resourceAsString(new ClassPathResource("pem/test-cert.pem"));
this.key = TestHelper.resourceAsString(new ClassPathResource("pem/test-key.pem"));
});
}

@Test
@DisplayName("contributes properties")
void test() {
void whenEnabled() {
new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);
assertThat(properties)
.containsEntry("spring.cloud.config.uri", "test-uri")
Expand All @@ -60,12 +76,136 @@ void test() {

@Test
@DisplayName("can be disabled")
void disabled() {
void whenDisabled() {
environment.setProperty("org.springframework.cloud.bindings.boot.config.enable", "false");

new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);

assertThat(properties).isEmpty();
}

@Test
@DisplayName("contributes tls key-store properties when set")
void whenKeystoreValuesSet() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.key", key)
.withEntry("tls.crt", cert)
)
);

new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);

assertThat(properties)
.containsEntry("spring.cloud.config.tls.enabled", true)
.containsEntry("spring.cloud.config.tls.key-store-type", "PKCS12")
.containsEntry("spring.cloud.config.tls.key-alias", "config")
.containsKey("spring.cloud.config.tls.key-store")
.containsKey("spring.cloud.config.tls.key-store-password")
.containsKey("spring.cloud.config.tls.key-password")
.doesNotContainKey("spring.cloud.config.tls.trust-store")
.doesNotContainKey("spring.cloud.config.tls.trust-store-type")
.doesNotContainKey("spring.cloud.config.tls.trust-store-password");

String path = properties.get("spring.cloud.config.tls.key-store").toString().substring(5);
File f = new File(path);
assertThat(f.exists()).isTrue();
assertThat(f.isFile()).isTrue();
}

@Test
@DisplayName("contributes tls trust-store properties when set")
void whenTruststoreValuesSet() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.key", key)
.withEntry("tls.crt", cert)
.withEntry("ca.crt", cert)
)
);

new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);

assertThat(properties)
.containsEntry("spring.cloud.config.tls.enabled", true)
.containsEntry("spring.cloud.config.tls.trust-store-type", "PKCS12")
.containsKey("spring.cloud.config.tls.trust-store")
.containsKey("spring.cloud.config.tls.trust-store-password");

String path = properties.get("spring.cloud.config.tls.trust-store").toString().substring(5);
File f = new File(path);
assertThat(f.exists()).isTrue();
assertThat(f.isFile()).isTrue();
}

@Test
@DisplayName("throws when bad tls key-store values are set")
void whenKeystoreValueIsNotValid() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.key", key)
.withEntry("tls.crt", "this isn't a valid certificate")
)
);

assertThrows(IllegalStateException.class, () -> {
new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);
});
}

@Test
@DisplayName("throws when bad tls trust-store values are set")
void whenTruststoreValueIsNotValid() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.key", key)
.withEntry("tls.crt", cert)
.withEntry("ca.crt", "this isn't a valid certificate")
)
);

assertThrows(IllegalStateException.class, () -> {
new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);
});
}

@Test
@DisplayName("throws when tls.crt is set but tls.key isn't")
void whenCertificateSetWithoutPrivateKey() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.crt", cert)
)
);

assertThrows(IllegalArgumentException.class, () -> {
new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);
});
}

@Test
@DisplayName("throws when tls.key is set but tls.crt isn't")
void whenPrivateKeySetWithoutCertificate() {
bindings = new Bindings(
new Binding("test-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, ConfigServerBindingsPropertiesProcessor.TYPE)
.withEntry("tls.key", key)
)
);

assertThrows(IllegalArgumentException.class, () -> {
new ConfigServerBindingsPropertiesProcessor().process(environment, bindings, properties);
});
}
}