Skip to content

Commit e9b5e16

Browse files
authored
Supports mTLS for config-server clients (#109)
* Supports mTLS for config-server clients * Common KeyStore codecleanup and refactoring Signed-off-by: kvmw <[email protected]> --------- Signed-off-by: kvmw <[email protected]>
1 parent 9be8ffb commit e9b5e16

File tree

6 files changed

+272
-75
lines changed

6 files changed

+272
-75
lines changed

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,21 @@ Disable Property: `org.springframework.cloud.bindings.boot.hana.enable`
273273
Type: `config`
274274
Disable Property: `org.springframework.cloud.bindings.boot.config.enable`
275275

276-
| Property | Value |
277-
| -------------------------------------------------- | -------------------- |
278-
| `spring.cloud.config.uri` | `{uri}` |
279-
| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` |
280-
| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` |
281-
| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` |
276+
| Property | Value |
277+
|------------------------------------------------------|--------------------------------------------------------|
278+
| `spring.cloud.config.uri` | `{uri}` |
279+
| `spring.cloud.config.client.oauth2.clientId` | `{client-id}` |
280+
| `spring.cloud.config.client.oauth2.clientSecret` | `{client-secret}` |
281+
| `spring.cloud.config.client.oauth2.accessTokenUri` | `{access-token-uri}` |
282+
| `spring.cloud.config.tls.enabled` | `true` when `{tls.crt}` and `{tls.key}` are set |
283+
| `spring.cloud.config.tls.key-store` | derived from `{tls.crt}` and `{tls.key}` |
284+
| `spring.cloud.config.tls.key-store-type` | `"PKCS12"` when `{tls.crt}` and `{tls.key}` are set |
285+
| `spring.cloud.config.tls.key-store-password` | random string when `{tls.crt}` and `{tls.key}` are set |
286+
| `spring.cloud.config.tls.key-alias` | `"config"` when `{tls.crt}` and `{tls.key}` are set |
287+
| `spring.cloud.config.tls.key-password` | `""` when `{tls.crt}` and `{tls.key}` are set |
288+
| `spring.cloud.config.tls.trust-store` | derived from `{ca.crt}` when it is set |
289+
| `spring.cloud.config.tls.trust-store-type` | `"PKCS12"` when `{ca.crt}` is set |
290+
| `spring.cloud.config.tls.trust-store-password` | random string when `{ca.crt}` is set |
282291

283292
## SCS Eureka
284293

spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/ConfigServerBindingsPropertiesProcessor.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818

1919
import org.springframework.cloud.bindings.Binding;
2020
import org.springframework.cloud.bindings.Bindings;
21+
import org.springframework.cloud.bindings.boot.pem.PemSslStoreHelper;
2122
import org.springframework.core.env.Environment;
23+
import org.springframework.util.StringUtils;
2224

25+
import java.nio.file.Path;
2326
import java.util.Map;
2427

28+
2529
import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;
2630

2731
/**
@@ -40,12 +44,42 @@ public void process(Environment environment, Bindings bindings, Map<String, Obje
4044
}
4145

4246
bindings.filterBindings(TYPE).forEach(binding -> {
43-
MapMapper map = new MapMapper(binding.getSecret(), properties);
47+
Map<String, String> secret = binding.getSecret();
48+
MapMapper map = new MapMapper(secret, properties);
4449
map.from("uri").to("spring.cloud.config.uri");
4550
map.from("client-id").to("spring.cloud.config.client.oauth2.clientId");
4651
map.from("client-secret").to("spring.cloud.config.client.oauth2.clientSecret");
4752
map.from("access-token-uri").to("spring.cloud.config.client.oauth2.accessTokenUri");
48-
});
4953

54+
// When tls.crt and tls.key are set, enable mTLS for config client.
55+
String clientKey = secret.get("tls.key");
56+
String clientCert = secret.get("tls.crt");
57+
if (StringUtils.hasText(clientCert) != StringUtils.hasText(clientKey)) {
58+
throw new IllegalArgumentException("binding secret error: tls.key and tls.crt must both be set if either is set");
59+
}
60+
61+
if (clientKey != null && !clientKey.isEmpty()) {
62+
String generatedPassword = PemSslStoreHelper.generatePassword();
63+
64+
// Create a keystore
65+
Path keyFilePath = PemSslStoreHelper.createKeyStoreFile("config-keystore", generatedPassword, clientCert, clientKey, "config");
66+
67+
properties.put("spring.cloud.config.tls.enabled", true);
68+
properties.put("spring.cloud.config.tls.key-alias", "config");
69+
properties.put("spring.cloud.config.tls.key-store", "file:" + keyFilePath);
70+
properties.put("spring.cloud.config.tls.key-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE);
71+
properties.put("spring.cloud.config.tls.key-store-password", generatedPassword);
72+
properties.put("spring.cloud.config.tls.key-password", "");
73+
74+
String caCert = secret.get("ca.crt");
75+
if (caCert != null && !caCert.isEmpty()) {
76+
// Create a truststore from the CA cert
77+
Path trustFilePath = PemSslStoreHelper.createKeyStoreFile("config-truststore", generatedPassword, caCert, null, "ca");
78+
properties.put("spring.cloud.config.tls.trust-store", "file:" + trustFilePath);
79+
properties.put("spring.cloud.config.tls.trust-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE);
80+
properties.put("spring.cloud.config.tls.trust-store-password", generatedPassword);
81+
}
82+
}
83+
});
5084
}
5185
}

spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/EurekaBindingsPropertiesProcessor.java

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,8 @@
2222
import org.springframework.cloud.bindings.boot.pem.PemSslStoreHelper;
2323
import org.springframework.util.StringUtils;
2424

25-
import java.io.*;
26-
import java.nio.file.Paths;
27-
import java.security.*;
28-
import java.security.cert.CertificateException;
25+
import java.nio.file.Path;
2926
import java.util.Map;
30-
import java.util.Random;
3127

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

@@ -66,19 +62,14 @@ public void process(Environment environment, Bindings bindings, Map<String, Obje
6662
properties.put("eureka.instance.preferIpAddress", true);
6763
}
6864

69-
Random random = new Random();
70-
String generatedPassword = random.ints(97 /* letter a */, 122 /* letter z */ + 1)
71-
.limit(10)
72-
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
73-
.toString();
65+
String generatedPassword = PemSslStoreHelper.generatePassword();
7466

7567
// Create a trust store from the CA cert
76-
String trustFilePath = Paths.get(System.getProperty("java.io.tmpdir"), "client-truststore.p12").toString();
77-
KeyStore trustStore = PemSslStoreHelper.createKeyStore("trust", "PKCS12", caCert, null, "rootca");
78-
createStoreFile("truststore", generatedPassword, trustFilePath, trustStore);
68+
Path trustFilePath = PemSslStoreHelper.createKeyStoreFile("eureka-truststore", generatedPassword, caCert, null, "rootca");
69+
7970
properties.put("eureka.client.tls.enabled", true);
8071
properties.put("eureka.client.tls.trust-store", "file:"+trustFilePath);
81-
properties.put("eureka.client.tls.trust-store-type", "PKCS12");
72+
properties.put("eureka.client.tls.trust-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE);
8273
properties.put("eureka.client.tls.trust-store-password", generatedPassword);
8374

8475
// When tls.crt and tls.key are set, enable mTLS for Eureka
@@ -90,41 +81,14 @@ public void process(Environment environment, Bindings bindings, Map<String, Obje
9081
if (clientKey != null && !clientKey.isEmpty()) {
9182

9283
// Create a keystore
93-
String keyFilePath = Paths.get(System.getProperty("java.io.tmpdir"), "client-keystore.p12").toString();
94-
KeyStore keyStore = PemSslStoreHelper.createKeyStore("key", "PKCS12", clientCert, clientKey, "eureka");
95-
createStoreFile("keystore", generatedPassword, keyFilePath, keyStore);
84+
Path keyFilePath = PemSslStoreHelper.createKeyStoreFile("eureka-keystore", generatedPassword, clientCert, clientKey, "eureka");
9685
properties.put("eureka.client.tls.key-alias", "eureka");
9786
properties.put("eureka.client.tls.key-store", "file:" + keyFilePath);
98-
properties.put("eureka.client.tls.key-store-type", "PKCS12");
87+
properties.put("eureka.client.tls.key-store-type", PemSslStoreHelper.PKCS12_STORY_TYPE);
9988
properties.put("eureka.client.tls.key-store-password", generatedPassword);
10089
properties.put("eureka.client.tls.key-password", "");
10190
}
10291
}
10392
});
10493
}
105-
106-
private static void createStoreFile(String storeType, String generatedPassword, String filePath, KeyStore ks) {
107-
try {
108-
FileOutputStream fos = new FileOutputStream(filePath);
109-
try {
110-
ks.store(fos, generatedPassword.toCharArray());
111-
} catch (KeyStoreException e) {
112-
throw new IllegalStateException("Unable to write " + storeType, e);
113-
} catch (NoSuchAlgorithmException e) {
114-
throw new IllegalStateException("Cryptographic algorithm not available", e);
115-
} catch (CertificateException e) {
116-
throw new IllegalStateException("Unable to process certificate", e);
117-
} catch (IOException e) {
118-
throw new IllegalStateException("Unable to create " + storeType, e);
119-
} finally {
120-
try {
121-
fos.close();
122-
} catch (IOException e) {
123-
throw new IllegalStateException("Unable to close " + storeType + " output file", e);
124-
}
125-
}
126-
} catch (FileNotFoundException e) {
127-
throw new IllegalStateException("Unable to open " + storeType + " output file", e);
128-
}
129-
}
13094
}

spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/pem/PemSslStoreHelper.java

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,76 @@
1616

1717
package org.springframework.cloud.bindings.boot.pem;
1818

19+
import java.io.FileOutputStream;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
1924
import java.security.KeyStore;
2025
import java.security.KeyStoreException;
26+
import java.security.NoSuchAlgorithmException;
2127
import java.security.PrivateKey;
28+
import java.security.cert.CertificateException;
2229
import java.security.cert.X509Certificate;
30+
import java.util.Random;
2331

2432
import org.springframework.util.Assert;
25-
import org.springframework.util.StringUtils;
2633

2734
/**
2835
* helper for creating stores from PEM-encoded certificates and private keys.
2936
*/
3037
public class PemSslStoreHelper {
38+
public static final String PKCS12_STORY_TYPE = "PKCS12";
3139
private static final String DEFAULT_KEY_ALIAS = "ssl";
3240

3341
/**
34-
* Utility method to create a KeyStore
35-
* @param name the name of the keystore
36-
* @param storeType the type of the keystore (JKS, PKCS12, etc.)
37-
* @param certificate a certificate as string that will be added to the keystore
38-
* @param privateKey the keystore private key as string
42+
* Utility method to create a KeyStore and save it in the tmp directory with give name.
43+
* @param name the store file name
44+
* @param password the store password
45+
* @param certificate the certificate to add to the store
46+
* @param privateKey the private key to add to the store
3947
* @param keyAlias the alias
40-
* @return the keystore
48+
* @return the path which store file is saved
4149
*/
42-
public static KeyStore createKeyStore(String name, String storeType, String certificate, String privateKey, String keyAlias) {
50+
public static Path createKeyStoreFile(String name, String password, String certificate, String privateKey, String keyAlias) {
51+
KeyStore store = createKeyStore(certificate, privateKey, keyAlias);
52+
53+
Path path;
54+
try {
55+
path = Files.createTempFile(Paths.get(System.getProperty("java.io.tmpdir")), name, ".p12");
56+
} catch (IOException e) {
57+
throw new IllegalStateException("Unable to create " + name, e);
58+
}
59+
60+
try (FileOutputStream fos = new FileOutputStream(path.toString())) {
61+
store.store(fos, password.toCharArray());
62+
} catch (KeyStoreException e) {
63+
throw new IllegalStateException("Unable to write " + name, e);
64+
} catch (NoSuchAlgorithmException e) {
65+
throw new IllegalStateException("Cryptographic algorithm not available", e);
66+
} catch (CertificateException e) {
67+
throw new IllegalStateException("Unable to process certificate", e);
68+
} catch (IOException e) {
69+
throw new IllegalStateException("Unable to create " + name, e);
70+
}
71+
return path;
72+
}
73+
74+
/**
75+
* Generates a password to use for KeyStore and/or TrustStore
76+
* @return the password
77+
*/
78+
public static String generatePassword() {
79+
return new Random().ints(97 /* letter a */, 122 /* letter z */ + 1)
80+
.limit(10)
81+
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
82+
.toString();
83+
}
84+
85+
private static KeyStore createKeyStore(String certificate, String privateKey, String keyAlias) {
4386
try {
4487
Assert.notNull(certificate, "CertificateContent must not be null");
45-
String type = StringUtils.hasText(storeType) ? storeType : KeyStore.getDefaultType();
46-
KeyStore store = KeyStore.getInstance(type);
88+
KeyStore store = KeyStore.getInstance(PKCS12_STORY_TYPE);
4789
store.load(null);
4890
String certificateContent = PemContent.load(certificate);
4991
String privateKeyContent = PemContent.load(privateKey);
@@ -53,7 +95,7 @@ public static KeyStore createKeyStore(String name, String storeType, String cert
5395
return store;
5496
}
5597
catch (Exception ex) {
56-
throw new IllegalStateException(String.format("Unable to create %s store: %s", name, ex.getMessage()), ex);
98+
throw new IllegalStateException(String.format("Unable to create key/trust store: %s", ex.getMessage()), ex);
5799
}
58100
}
59101

0 commit comments

Comments
 (0)