From d56038e3e6a98179165af40b96afd78d6aa3d483 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 25 Jul 2020 22:41:09 +0800 Subject: [PATCH 01/10] add client side TLS cert support --- pom.xml | 7 + spring-cloud-config-client-tls-tests/pom.xml | 111 +++++++++ .../cloud/config/client/AppRunner.java | 122 +++++++++ .../cloud/config/client/BaseCertTest.java | 95 +++++++ .../cloud/config/client/ConfigClientTest.java | 149 +++++++++++ .../cloud/config/client/KeyAndCert.java | 91 +++++++ .../cloud/config/client/KeyTool.java | 116 +++++++++ .../config/client/TlsConfigClientRunner.java | 60 +++++ .../config/client/TlsConfigServerRunner.java | 51 ++++ .../test/config/application.properties | 1 + spring-cloud-config-client/pom.xml | 4 + .../config/client/ConfigClientProperties.java | 231 ++++++++++++++++++ .../ConfigServicePropertySourceLocator.java | 36 ++- 13 files changed, 1070 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-config-client-tls-tests/pom.xml create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java create mode 100644 spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties diff --git a/pom.xml b/pom.xml index 9b570380..77d885e7 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 2.2.4.BUILD-SNAPSHOT 1.11.52 v1-rev20191010-1.30.3 + 1.64 true true @@ -43,6 +44,7 @@ spring-cloud-config-monitor spring-cloud-config-sample spring-cloud-starter-config + spring-cloud-config-client-tls-tests docs @@ -87,6 +89,11 @@ google-auth-library-oauth2-http 0.15.0 + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml new file mode 100644 index 00000000..e442991f --- /dev/null +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + spring-cloud-config-client-tls-tests + jar + Spring Cloud Config Client TLS Tests + + + org.springframework.cloud + spring-cloud-config + 3.0.0-SNAPSHOT + .. + + + https://spring.io + + + + + + + org.springframework.cloud + spring-cloud-config-client + ${project.version} + + + org.springframework.cloud + spring-cloud-config-server + ${project.version} + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-starter-logging + true + + + org.springframework.cloud + spring-cloud-commons + + + org.springframework.cloud + spring-cloud-context + + + org.springframework + spring-web + + + com.fasterxml.jackson.core + jackson-annotations + + + org.springframework.retry + spring-retry + true + + + org.springframework.boot + spring-boot-starter-actuator + true + + + org.springframework.boot + spring-boot-starter-aop + true + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.cloud + spring-cloud-test-support + test + + + org.bouncycastle + bcpkix-jdk15on + test + + + + diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java new file mode 100644 index 00000000..2c9d950a --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java @@ -0,0 +1,122 @@ +/* + * 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 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 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 getBean(Class type) { + return app.getBean(type); + } + + public ApplicationContext parent() { + return app.getParent(); + } + + public Map getParentBeans(Class 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(); + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java new file mode 100644 index 00000000..dd0d85fe --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java @@ -0,0 +1,95 @@ +/* + * 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 trustStore; + + 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); + trustStore = saveTrustStore(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", trustStore.getAbsolutePath()); + System.setProperty("javax.net.ssl.trustStoreType", "JKS"); + } + + 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; + } + + private static File saveTrustStore(KeyAndCert keyCert) throws Exception { + File result = File.createTempFile(keyCert.subject(), "jks"); + try (OutputStream output = new FileOutputStream(result)) { + KeyStore store = keyCert.storeCert("JKS"); + store.store(output, new char [0]); + } + return result; + } + + interface KeyStoreSupplier { + + public KeyStore createKeyStore() throws Exception; + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java new file mode 100644 index 00000000..bdfafa07 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java @@ -0,0 +1,149 @@ +/* + * 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 static org.junit.Assert.*; + +import java.io.File; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.config.server.EnableConfigServer; + +public class ConfigClientTest extends BaseCertTest { + + private static TlsConfigServerRunner server; + + @BeforeClass + public static void setupAll() throws Exception { + startConfigServer(); + } + + @AfterClass + public static void tearDownAll() { + stopConfigServer(); + } + + private static void startConfigServer() { + server = new TlsConfigServerRunner(TestConfigServer.class); + server.enableTls(); + server.setKeyStore(serverCert, KEY_STORE_PASSWORD, "server", KEY_PASSWORD); + server.setTrustStore(caCert, KEY_STORE_PASSWORD); + + server.start(); + } + + private static void stopConfigServer() { + server.stop(); + } + + @Test + public void clientCertCanWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.start(); + assertEquals("dumb-value", client.getProperty("dumb.key")); + } + } + + @Test + public void tlsClientCanBeDisabled() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.property("spring.cloud.config.enabled", "false"); + client.start(); + assertNull(client.getProperty("dumb.key")); + } + } + + @Test + public void noCertCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + client.disableTls(); + client.start(); + assertNull(client.getProperty("dumb.key")); + } + } + + @Test + public void wrongCertCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.setKeyStore(wrongClientCert); + client.start(); + assertNull(client.getProperty("dumb.key")); + } + } + + @Test(expected = IllegalStateException.class) + public void wrongPasswordCauseFailure() { + TlsConfigClientRunner client = createConfigClient(); + enableTlsClient(client); + client.setKeyStore(clientCert, WRONG_PASSWORD, WRONG_PASSWORD); + client.start(); + } + + @Test(expected = IllegalStateException.class) + public void nonExistKeyStoreCauseFailure() { + TlsConfigClientRunner client = createConfigClient(); + enableTlsClient(client); + client.setKeyStore(new File("nonExistFile")); + client.start(); + } + + @Test + public void wrongTrustStoreCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.setTrustStore(wrongCaCert); + client.start(); + assertNull(client.getProperty("dumb.key")); + } + } + + @Test + public void onlyOneLocator() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.start(); + + assertEquals(1, client.getParentBeans(ConfigServicePropertySourceLocator.class).size()); + } + } + + private TlsConfigClientRunner createConfigClient() { + return new TlsConfigClientRunner(TestApp.class, server); + } + + private void enableTlsClient(TlsConfigClientRunner runner) { + runner.enableTls(); + runner.setKeyStore(clientCert, KEY_STORE_PASSWORD, KEY_PASSWORD); + runner.setTrustStore(caCert, KEY_STORE_PASSWORD); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + public static class TestApp {} + + @SpringBootConfiguration + @EnableAutoConfiguration + @EnableConfigServer + public static class TestConfigServer {} +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java new file mode 100644 index 00000000..218a5eb2 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java @@ -0,0 +1,91 @@ +/* + * 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.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class KeyAndCert { + + private KeyPair keyPair; + private X509Certificate certificate; + + public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair keyPair() { + return keyPair; + } + + public PublicKey publicKey() { + return keyPair.getPublic(); + } + + public PrivateKey privateKey() { + return keyPair.getPrivate(); + } + + public X509Certificate certificate() { + return certificate; + } + + public String subject() { + String dn = certificate.getSubjectDN().getName(); + int index = dn.indexOf('='); + return dn.substring(index + 1); + } + + public KeyAndCert sign(String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(subject, this); + } + + public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(keyPair, subject, this); + } + + public KeyStore storeKeyAndCert(String keyPassword) throws Exception { + KeyStore result = KeyStore.getInstance("PKCS12"); + result.load(null); + + result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), certChain()); + return result; + } + + private Certificate [] certChain() { + return new Certificate [] {certificate()}; + } + + public KeyStore storeCert() throws Exception { + return storeCert("PKCS12"); + } + + public KeyStore storeCert(String storeType) throws Exception { + KeyStore result = KeyStore.getInstance(storeType); + result.load(null); + + result.setCertificateEntry(subject(), certificate()); + return result; + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java new file mode 100644 index 00000000..fd13c701 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java @@ -0,0 +1,116 @@ +/* + * 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.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Date; + +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +public class KeyTool { + + private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + private static final long TEN_YEARS = ONE_DAY * 365L * 10L; + + public KeyAndCert createCA(String ca) throws Exception { + KeyPair keyPair = createKeyPair(); + X509Certificate certificate = createCert(keyPair, ca); + return new KeyAndCert(keyPair, certificate); + } + + public KeyAndCert signCertificate(String subject, KeyAndCert signer) throws Exception { + return signCertificate(createKeyPair(), subject, signer); + } + + public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) throws Exception { + X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), signer.subject(), subject); + KeyAndCert result = new KeyAndCert(keyPair, certificate); + + return result; + } + + public KeyPair createKeyPair() throws Exception { + return createKeyPair(1024); + } + + public KeyPair createKeyPair(int keySize) throws Exception { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keySize, new SecureRandom()); + return gen.generateKeyPair(); + } + + public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign)); + builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true)); + + return signCert(builder, keyPair.getPrivate()); + } + + public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, String issuer, String subject) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false)); + + GeneralName [] names = new GeneralName [] { new GeneralName(GeneralName.dNSName, "localhost") }; + builder.addExtension(Extension.subjectAlternativeName, false, GeneralNames.getInstance(new DERSequence(names))); + + return signCert(builder, privateKey); + } + + private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, String subject) { + X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); + X500Name subjectName = new X500Name(String.format("dc=%s", subject)); + + long now = System.currentTimeMillis(); + BigInteger serialNum = BigInteger.valueOf(now); + Date notBefore = new Date(now - ONE_DAY); + Date notAfter = new Date(now + TEN_YEARS); + + return new JcaX509v3CertificateBuilder( + issuerName, + serialNum, + notBefore, + notAfter, + subjectName, + publicKey); + } + + private X509Certificate signCert(JcaX509v3CertificateBuilder builder, PrivateKey privateKey) throws Exception { + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); + X509CertificateHolder holder = builder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(holder); + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java new file mode 100644 index 00000000..c1ef9446 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java @@ -0,0 +1,60 @@ +/* + * 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; + +public class TlsConfigClientRunner extends AppRunner { + + public TlsConfigClientRunner(Class appClass, AppRunner server) { + super(appClass); + + property("spring.cloud.config.uri", server.root()); + property("spring.cloud.config.enabled", "true"); + } + + public void enableTls() { + property("spring.cloud.config.tls.enabled", "true"); + } + + public void disableTls() { + property("spring.cloud.config.tls.enabled", "false"); + } + + public void setKeyStore(File keyStore, String keyStorePassword, String keyPassword) { + property("spring.cloud.config.tls.key-store", pathOf(keyStore)); + property("spring.cloud.config.tls.key-store-password", keyStorePassword); + property("spring.cloud.config.tls.key-password", keyPassword); + } + + public void setKeyStore(File keyStore) { + property("spring.cloud.config.tls.key-store", pathOf(keyStore)); + } + + public void setTrustStore(File trustStore, String password) { + property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); + property("spring.cloud.config.tls.trust-store-password", password); + } + + public void setTrustStore(File trustStore) { + property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); + } + + private String pathOf(File file) { + return String.format("file:%s", file.getAbsolutePath()); + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java new file mode 100644 index 00000000..8d435240 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java @@ -0,0 +1,51 @@ +/* + * 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; + +public class TlsConfigServerRunner extends AppRunner { + + public TlsConfigServerRunner(Class appClass) { + super(appClass); + property("spring.profiles.active", "native"); + property("spring.cloud.config.server.native.search-locations", "classpath:/test/config"); + } + + public void enableTls() { + property("server.ssl.enabled", "true"); + property("server.ssl.client-auth", "need"); + } + + public void setKeyStore(File keyStore, String keyStorePassword, String key, String keyPassword) { + property("server.ssl.key-store", pathOf(keyStore)); + property("server.ssl.key-store-type", "PKCS12"); + property("server.ssl.key-store-password", keyStorePassword); + property("server.ssl.key-alias", key); + property("server.ssl.key-password", keyPassword); + } + + public void setTrustStore(File trustStore, String password) { + property("server.ssl.trust-store", pathOf(trustStore)); + property("server.ssl.trust-store-type", "PKCS12"); + property("server.ssl.trust-store-password", password); + } + + private String pathOf(File file) { + return String.format("file:%s", file.getAbsolutePath()); + } +} diff --git a/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties b/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties new file mode 100644 index 00000000..c5344796 --- /dev/null +++ b/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties @@ -0,0 +1 @@ +dumb.key=dumb-value \ No newline at end of file diff --git a/spring-cloud-config-client/pom.xml b/spring-cloud-config-client/pom.xml index a3d7d09d..e80ec4e4 100644 --- a/spring-cloud-config-client/pom.xml +++ b/spring-cloud-config-client/pom.xml @@ -71,6 +71,10 @@ com.fasterxml.jackson.core jackson-databind + + org.apache.httpcomponents + httpclient + org.springframework.boot spring-boot-autoconfigure-processor diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java index 11b215b6..d6c84bbe 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java @@ -16,16 +16,28 @@ package org.springframework.cloud.config.client; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.UnrecoverableKeyException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import javax.annotation.PostConstruct; +import javax.net.ssl.SSLContext; + +import org.apache.http.ssl.SSLContextBuilder; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.env.Environment; +import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; @@ -98,6 +110,11 @@ public class ConfigClientProperties { * Discovery properties. */ private Discovery discovery = new Discovery(); + + /** + * TLS properties + */ + private TLS tls = new TLS(); /** * Flag to indicate that failure to connect to the server is fatal (default false). @@ -208,6 +225,19 @@ public class ConfigClientProperties { this.discovery = discovery; } + public TLS getTls() { + return tls; + } + + public void setTls(TLS tls) { + this.tls = tls; + } + + @PostConstruct + public void checkTlsStoreType() { + tls.checkStoreType(); + } + public boolean isFailFast() { return this.failFast; } @@ -414,5 +444,206 @@ public class ConfigClientProperties { } } + + /** + * TLS properties + */ + public static class TLS { + + private static final String DEFAULT_STORE_TYPE = "PKCS12"; + private static final Map EXTENSION_STORE_TYPES = extTypes(); + + private boolean enabled; + + private Resource keyStore; + private String keyStoreType; + private String keyStorePassword = ""; + private String keyPassword = ""; + + private Resource trustStore; + private String trustStoreType; + private String trustStorePassword = ""; + + private static Map extTypes() { + Map result = new HashMap<>(); + + result.put("p12", "PKCS12"); + result.put("pfx", "PKCS12"); + result.put("jks", "JKS"); + + return Collections.unmodifiableMap(result); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Resource getKeyStore() { + return keyStore; + } + + public void setKeyStore(Resource keyStore) { + this.keyStore = keyStore; + } + + public String getKeyStoreType() { + return keyStoreType; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getKeyPassword() { + return keyPassword; + } + + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + public Resource getTrustStore() { + return trustStore; + } + + public void setTrustStore(Resource trustStore) { + this.trustStore = trustStore; + } + + public String getTrustStoreType() { + return trustStoreType; + } + + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public char[] keyStorePassword() { + return keyStorePassword.toCharArray(); + } + + public char[] keyPassword() { + return keyPassword.toCharArray(); + } + + public char[] trustStorePassword() { + return trustStorePassword.toCharArray(); + } + + public void checkStoreType() { + if (keyStore != null && keyStoreType == null) { + keyStoreType = storeTypeOf(keyStore); + } + if (trustStore != null && trustStoreType == null) { + trustStoreType = storeTypeOf(trustStore); + } + } + + private String storeTypeOf(Resource resource) { + String extension = fileExtensionOf(resource); + String type = EXTENSION_STORE_TYPES.get(extension); + + return (type == null) ? DEFAULT_STORE_TYPE : type; + } + + private String fileExtensionOf(Resource resource) { + String name = resource.getFilename(); + int index = name.lastIndexOf('.'); + + return index < 0 ? "" : name.substring(index + 1).toLowerCase(); + } + + public SSLContext createSSLContext() throws GeneralSecurityException, IOException { + SSLContextBuilder builder = new SSLContextBuilder(); + char[] keyPassword = keyPassword(); + KeyStore keyStore = createKeyStore(); + + try { + builder.loadKeyMaterial(keyStore, keyPassword); + } catch (UnrecoverableKeyException e) { + if (keyPassword.length == 0) { + // Retry if empty password, see https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + builder.loadKeyMaterial(keyStore, new char[]{'\0'}); + } else { + throw e; + } + } + + KeyStore trust = createTrustStore(); + if (trust != null) { + builder.loadTrustMaterial(trust, null); + } + + return builder.build(); + } + + private KeyStore createKeyStore() throws GeneralSecurityException, IOException { + if (keyStore == null) { + throw new KeyStoreException("Keystore not specified."); + } + if (!keyStore.exists()) { + throw new KeyStoreException("Keystore not exists: " + keyStore); + } + + KeyStore result = KeyStore.getInstance(keyStoreType); + char[] keyStorePassword = keyStorePassword(); + + try { + loadKeyStore(result, keyStore, keyStorePassword); + } catch (IOException e) { + // Retry if empty password, see https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + if (keyStorePassword.length == 0) { + loadKeyStore(result, keyStore, new char[]{'\0'}); + } else { + throw e; + } + } + + return result; + } + + private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, char[] keyStorePassword) + throws IOException, GeneralSecurityException + { + try (InputStream inputStream = keyStoreResource.getInputStream()) { + keyStore.load(inputStream, keyStorePassword); + } + } + + private KeyStore createTrustStore() throws GeneralSecurityException, IOException { + if (trustStore == null) { + return null; + } + if (!trustStore.exists()) { + throw new KeyStoreException("KeyStore not exists: " + trustStore); + } + + KeyStore result = KeyStore.getInstance(trustStoreType); + try (InputStream input = trustStore.getInputStream()) { + result.load(input, trustStorePassword()); + } + return result; + } + } } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java index 2e3d5061..171cc246 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java @@ -17,6 +17,7 @@ package org.springframework.cloud.config.client; import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -26,9 +27,12 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import javax.net.ssl.SSLContext; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClients; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginTrackedValue; @@ -48,8 +52,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.retry.annotation.Retryable; import org.springframework.util.Assert; @@ -296,15 +302,14 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator } private RestTemplate getSecureRestTemplate(ConfigClientProperties client) { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); if (client.getRequestReadTimeout() < 0) { throw new IllegalStateException("Invalid Value for Read Timeout set."); } if (client.getRequestConnectTimeout() < 0) { throw new IllegalStateException("Invalid Value for Connect Timeout set."); } - requestFactory.setReadTimeout(client.getRequestReadTimeout()); - requestFactory.setConnectTimeout(client.getRequestConnectTimeout()); + + ClientHttpRequestFactory requestFactory = createHttpRquestFactory(client); RestTemplate template = new RestTemplate(requestFactory); Map headers = new HashMap<>(client.getHeaders()); if (headers.containsKey(AUTHORIZATION)) { @@ -317,6 +322,29 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator return template; } + + private ClientHttpRequestFactory createHttpRquestFactory(ConfigClientProperties client) { + if (client.getTls().isEnabled()) { + try { + SSLContext sslContext = client.getTls().createSSLContext(); + HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); + HttpComponentsClientHttpRequestFactory result = new HttpComponentsClientHttpRequestFactory(httpClient); + + result.setReadTimeout(client.getRequestReadTimeout()); + result.setConnectTimeout(client.getRequestConnectTimeout()); + return result; + + } catch (GeneralSecurityException | IOException ex) { + logger.error(ex); + throw new IllegalStateException("Failed to create config client with TLS.", ex); + } + } + + SimpleClientHttpRequestFactory result = new SimpleClientHttpRequestFactory(); + result.setReadTimeout(client.getRequestReadTimeout()); + result.setConnectTimeout(client.getRequestConnectTimeout()); + return result; + } private void addAuthorizationToken(ConfigClientProperties configClientProperties, HttpHeaders httpHeaders, String username, String password) { From f28ea277f7c2751a9acf63860302a789d294359e Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 25 Jul 2020 22:46:26 +0800 Subject: [PATCH 02/10] Update BaseCertTest.java --- .../cloud/config/client/BaseCertTest.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java index dd0d85fe..2d252118 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java @@ -31,7 +31,6 @@ public class BaseCertTest { protected static File caCert; protected static File wrongCaCert; - protected static File trustStore; protected static File serverCert; protected static File clientCert; @@ -46,7 +45,6 @@ public class BaseCertTest { KeyAndCert client = ca.sign("client"); caCert = saveCert(ca); - trustStore = saveTrustStore(ca); serverCert = saveKeyAndCert(server); clientCert = saveKeyAndCert(client); @@ -56,8 +54,8 @@ public class BaseCertTest { wrongCaCert = saveCert(wrongCa); wrongClientCert = saveKeyAndCert(wrongClient); - System.setProperty("javax.net.ssl.trustStore", trustStore.getAbsolutePath()); - System.setProperty("javax.net.ssl.trustStoreType", "JKS"); + 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 { @@ -79,15 +77,6 @@ public class BaseCertTest { return result; } - private static File saveTrustStore(KeyAndCert keyCert) throws Exception { - File result = File.createTempFile(keyCert.subject(), "jks"); - try (OutputStream output = new FileOutputStream(result)) { - KeyStore store = keyCert.storeCert("JKS"); - store.store(output, new char [0]); - } - return result; - } - interface KeyStoreSupplier { public KeyStore createKeyStore() throws Exception; From 67a6cbfe23391dfc256c55b4080e58b6fbfc3e91 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sun, 26 Jul 2020 02:01:59 +0800 Subject: [PATCH 03/10] change format for check style --- spring-cloud-config-client-tls-tests/pom.xml | 31 ++- .../cloud/config/client/AppRunner.java | 183 ++++++------- .../cloud/config/client/BaseCertTest.java | 123 +++++---- .../cloud/config/client/ConfigClientTest.java | 236 ++++++++--------- .../cloud/config/client/KeyAndCert.java | 131 ++++----- .../cloud/config/client/KeyTool.java | 158 +++++------ .../config/client/TlsConfigClientRunner.java | 75 +++--- .../config/client/TlsConfigServerRunner.java | 59 +++-- .../test/config/application.properties | 2 +- .../config/client/ConfigClientProperties.java | 250 +++++++++--------- .../ConfigServicePropertySourceLocator.java | 40 +-- 11 files changed, 670 insertions(+), 618 deletions(-) diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index e442991f..40ab9edd 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -20,30 +20,37 @@ This project is a Spring configuration client. ]]> + + + 2.4.0-M1 + - - org.springframework.cloud - spring-cloud-config-client - ${project.version} - org.springframework.cloud spring-cloud-config-server ${project.version} + + org.springframework.cloud + spring-cloud-config-client + ${project.version} + + org.springframework.boot - spring-boot-configuration-processor - true + spring-boot + ${spring.boot.version} org.springframework.boot spring-boot-autoconfigure + ${spring.boot.version} org.springframework.boot spring-boot-starter-logging + ${spring.boot.version} true @@ -70,11 +77,13 @@ org.springframework.boot spring-boot-starter-actuator + ${spring.boot.version} true org.springframework.boot spring-boot-starter-aop + ${spring.boot.version} true @@ -84,23 +93,21 @@ org.springframework.boot spring-boot-autoconfigure-processor + ${spring.boot.version} true org.springframework.boot spring-boot-starter-test + ${spring.boot.version} test + org.junit.vintage junit-vintage-engine test - - org.springframework.cloud - spring-cloud-test-support - test - org.bouncycastle bcpkix-jdk15on diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java index 2c9d950a..b7b719c3 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/AppRunner.java @@ -28,95 +28,98 @@ import org.springframework.util.SocketUtils; public class AppRunner implements AutoCloseable { - private Class appClass; - private Map 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 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 getBean(Class type) { - return app.getBean(type); - } - - public ApplicationContext parent() { - return app.getParent(); - } - - public Map getParentBeans(Class 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); - } + private Class appClass; + + private Map 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 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 getBean(Class type) { + return app.getBean(type); + } + + public ApplicationContext parent() { + return app.getParent(); + } + + public Map getParentBeans(Class 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(); + } - @Override - public void close() { - stop(); - } } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java index 2d252118..bf0a555e 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/BaseCertTest.java @@ -24,61 +24,70 @@ 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 { - - public KeyStore createKeyStore() throws Exception; - } + + 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; + + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java index bdfafa07..38f35ee1 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java @@ -16,134 +16,130 @@ package org.springframework.cloud.config.client; -import static org.junit.Assert.*; - import java.io.File; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; + import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.config.server.EnableConfigServer; +import static org.assertj.core.api.Assertions.assertThat; + public class ConfigClientTest extends BaseCertTest { - - private static TlsConfigServerRunner server; - - @BeforeClass - public static void setupAll() throws Exception { - startConfigServer(); - } - - @AfterClass - public static void tearDownAll() { - stopConfigServer(); - } - - private static void startConfigServer() { - server = new TlsConfigServerRunner(TestConfigServer.class); - server.enableTls(); - server.setKeyStore(serverCert, KEY_STORE_PASSWORD, "server", KEY_PASSWORD); - server.setTrustStore(caCert, KEY_STORE_PASSWORD); - - server.start(); - } - - private static void stopConfigServer() { - server.stop(); - } - - @Test - public void clientCertCanWork() { - try (TlsConfigClientRunner client = createConfigClient()) { - enableTlsClient(client); - client.start(); - assertEquals("dumb-value", client.getProperty("dumb.key")); - } - } - - @Test - public void tlsClientCanBeDisabled() { - try (TlsConfigClientRunner client = createConfigClient()) { - enableTlsClient(client); - client.property("spring.cloud.config.enabled", "false"); - client.start(); - assertNull(client.getProperty("dumb.key")); - } - } - - @Test - public void noCertCannotWork() { - try (TlsConfigClientRunner client = createConfigClient()) { - client.disableTls(); - client.start(); - assertNull(client.getProperty("dumb.key")); - } - } - - @Test - public void wrongCertCannotWork() { - try (TlsConfigClientRunner client = createConfigClient()) { - enableTlsClient(client); - client.setKeyStore(wrongClientCert); - client.start(); - assertNull(client.getProperty("dumb.key")); - } - } - - @Test(expected = IllegalStateException.class) - public void wrongPasswordCauseFailure() { - TlsConfigClientRunner client = createConfigClient(); - enableTlsClient(client); - client.setKeyStore(clientCert, WRONG_PASSWORD, WRONG_PASSWORD); - client.start(); - } - - @Test(expected = IllegalStateException.class) - public void nonExistKeyStoreCauseFailure() { - TlsConfigClientRunner client = createConfigClient(); - enableTlsClient(client); - client.setKeyStore(new File("nonExistFile")); - client.start(); - } - - @Test - public void wrongTrustStoreCannotWork() { - try (TlsConfigClientRunner client = createConfigClient()) { - enableTlsClient(client); - client.setTrustStore(wrongCaCert); - client.start(); - assertNull(client.getProperty("dumb.key")); - } - } - - @Test - public void onlyOneLocator() { - try (TlsConfigClientRunner client = createConfigClient()) { - enableTlsClient(client); - client.start(); - - assertEquals(1, client.getParentBeans(ConfigServicePropertySourceLocator.class).size()); - } - } - - private TlsConfigClientRunner createConfigClient() { - return new TlsConfigClientRunner(TestApp.class, server); - } - - private void enableTlsClient(TlsConfigClientRunner runner) { - runner.enableTls(); - runner.setKeyStore(clientCert, KEY_STORE_PASSWORD, KEY_PASSWORD); - runner.setTrustStore(caCert, KEY_STORE_PASSWORD); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - public static class TestApp {} - - @SpringBootConfiguration - @EnableAutoConfiguration - @EnableConfigServer - public static class TestConfigServer {} + + private static TlsConfigServerRunner server; + + @BeforeClass + public static void setupAll() throws Exception { + startConfigServer(); + } + + @AfterClass + public static void tearDownAll() { + stopConfigServer(); + } + + private static void startConfigServer() { + server = new TlsConfigServerRunner(TestConfigServer.class); + server.enableTls(); + server.setKeyStore(serverCert, KEY_STORE_PASSWORD, "server", KEY_PASSWORD); + server.setTrustStore(caCert, KEY_STORE_PASSWORD); + + server.start(); + } + + private static void stopConfigServer() { + server.stop(); + } + + @Test + public void clientCertCanWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.start(); + assertThat(client.getProperty("dumb.key")).isEqualTo("dumb-value"); + } + } + + @Test + public void tlsClientCanBeDisabled() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.property("spring.cloud.config.enabled", "false"); + client.start(); + assertThat(client.getProperty("dumb.key")).isNull(); + } + } + + @Test + public void noCertCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + client.disableTls(); + client.start(); + assertThat(client.getProperty("dumb.key")).isNull(); + } + } + + @Test + public void wrongCertCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.setKeyStore(wrongClientCert); + client.start(); + assertThat(client.getProperty("dumb.key")).isNull(); + } + } + + @Test(expected = IllegalStateException.class) + public void wrongPasswordCauseFailure() { + TlsConfigClientRunner client = createConfigClient(); + enableTlsClient(client); + client.setKeyStore(clientCert, WRONG_PASSWORD, WRONG_PASSWORD); + client.start(); + } + + @Test(expected = IllegalStateException.class) + public void nonExistKeyStoreCauseFailure() { + TlsConfigClientRunner client = createConfigClient(); + enableTlsClient(client); + client.setKeyStore(new File("nonExistFile")); + client.start(); + } + + @Test + public void wrongTrustStoreCannotWork() { + try (TlsConfigClientRunner client = createConfigClient()) { + enableTlsClient(client); + client.setTrustStore(wrongCaCert); + client.start(); + assertThat(client.getProperty("dumb.key")).isNull(); + } + } + + private TlsConfigClientRunner createConfigClient() { + return new TlsConfigClientRunner(TestApp.class, server); + } + + private void enableTlsClient(TlsConfigClientRunner runner) { + runner.enableTls(); + runner.setKeyStore(clientCert, KEY_STORE_PASSWORD, KEY_PASSWORD); + runner.setTrustStore(caCert, KEY_STORE_PASSWORD); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + public static class TestApp { + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @EnableConfigServer + public static class TestConfigServer { + + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java index 218a5eb2..4c58a125 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyAndCert.java @@ -24,68 +24,71 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; public class KeyAndCert { - - private KeyPair keyPair; - private X509Certificate certificate; - - public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { - this.keyPair = keyPair; - this.certificate = certificate; - } - - public KeyPair keyPair() { - return keyPair; - } - - public PublicKey publicKey() { - return keyPair.getPublic(); - } - - public PrivateKey privateKey() { - return keyPair.getPrivate(); - } - - public X509Certificate certificate() { - return certificate; - } - - public String subject() { - String dn = certificate.getSubjectDN().getName(); - int index = dn.indexOf('='); - return dn.substring(index + 1); - } - - public KeyAndCert sign(String subject) throws Exception { - KeyTool tool = new KeyTool(); - return tool.signCertificate(subject, this); - } - - public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { - KeyTool tool = new KeyTool(); - return tool.signCertificate(keyPair, subject, this); - } - - public KeyStore storeKeyAndCert(String keyPassword) throws Exception { - KeyStore result = KeyStore.getInstance("PKCS12"); - result.load(null); - - result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), certChain()); - return result; - } - - private Certificate [] certChain() { - return new Certificate [] {certificate()}; - } - - public KeyStore storeCert() throws Exception { - return storeCert("PKCS12"); - } - - public KeyStore storeCert(String storeType) throws Exception { - KeyStore result = KeyStore.getInstance(storeType); - result.load(null); - - result.setCertificateEntry(subject(), certificate()); - return result; - } + + private KeyPair keyPair; + + private X509Certificate certificate; + + public KeyAndCert(KeyPair keyPair, X509Certificate certificate) { + this.keyPair = keyPair; + this.certificate = certificate; + } + + public KeyPair keyPair() { + return keyPair; + } + + public PublicKey publicKey() { + return keyPair.getPublic(); + } + + public PrivateKey privateKey() { + return keyPair.getPrivate(); + } + + public X509Certificate certificate() { + return certificate; + } + + public String subject() { + String dn = certificate.getSubjectDN().getName(); + int index = dn.indexOf('='); + return dn.substring(index + 1); + } + + public KeyAndCert sign(String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(subject, this); + } + + public KeyAndCert sign(KeyPair keyPair, String subject) throws Exception { + KeyTool tool = new KeyTool(); + return tool.signCertificate(keyPair, subject, this); + } + + public KeyStore storeKeyAndCert(String keyPassword) throws Exception { + KeyStore result = KeyStore.getInstance("PKCS12"); + result.load(null); + + result.setKeyEntry(subject(), keyPair.getPrivate(), keyPassword.toCharArray(), + certChain()); + return result; + } + + private Certificate[] certChain() { + return new Certificate[] { certificate() }; + } + + public KeyStore storeCert() throws Exception { + return storeCert("PKCS12"); + } + + public KeyStore storeCert(String storeType) throws Exception { + KeyStore result = KeyStore.getInstance(storeType); + result.load(null); + + result.setCertificateEntry(subject(), certificate()); + return result; + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java index fd13c701..1ee56ea3 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/KeyTool.java @@ -39,78 +39,88 @@ import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; public class KeyTool { - - private static final long ONE_DAY = 1000L * 60L * 60L * 24L; - private static final long TEN_YEARS = ONE_DAY * 365L * 10L; - - public KeyAndCert createCA(String ca) throws Exception { - KeyPair keyPair = createKeyPair(); - X509Certificate certificate = createCert(keyPair, ca); - return new KeyAndCert(keyPair, certificate); - } - - public KeyAndCert signCertificate(String subject, KeyAndCert signer) throws Exception { - return signCertificate(createKeyPair(), subject, signer); - } - - public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) throws Exception { - X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), signer.subject(), subject); - KeyAndCert result = new KeyAndCert(keyPair, certificate); - - return result; - } - - public KeyPair createKeyPair() throws Exception { - return createKeyPair(1024); - } - - public KeyPair createKeyPair(int keySize) throws Exception { - KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); - gen.initialize(keySize, new SecureRandom()); - return gen.generateKeyPair(); - } - - public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { - JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); - builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign)); - builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true)); - - return signCert(builder, keyPair.getPrivate()); - } - - public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, String issuer, String subject) throws Exception { - JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); - builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); - builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false)); - - GeneralName [] names = new GeneralName [] { new GeneralName(GeneralName.dNSName, "localhost") }; - builder.addExtension(Extension.subjectAlternativeName, false, GeneralNames.getInstance(new DERSequence(names))); - - return signCert(builder, privateKey); - } - - private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, String subject) { - X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); - X500Name subjectName = new X500Name(String.format("dc=%s", subject)); - - long now = System.currentTimeMillis(); - BigInteger serialNum = BigInteger.valueOf(now); - Date notBefore = new Date(now - ONE_DAY); - Date notAfter = new Date(now + TEN_YEARS); - - return new JcaX509v3CertificateBuilder( - issuerName, - serialNum, - notBefore, - notAfter, - subjectName, - publicKey); - } - - private X509Certificate signCert(JcaX509v3CertificateBuilder builder, PrivateKey privateKey) throws Exception { - ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); - X509CertificateHolder holder = builder.build(signer); - - return new JcaX509CertificateConverter().getCertificate(holder); - } + + private static final long ONE_DAY = 1000L * 60L * 60L * 24L; + + private static final long TEN_YEARS = ONE_DAY * 365L * 10L; + + public KeyAndCert createCA(String ca) throws Exception { + KeyPair keyPair = createKeyPair(); + X509Certificate certificate = createCert(keyPair, ca); + return new KeyAndCert(keyPair, certificate); + } + + public KeyAndCert signCertificate(String subject, KeyAndCert signer) + throws Exception { + return signCertificate(createKeyPair(), subject, signer); + } + + public KeyAndCert signCertificate(KeyPair keyPair, String subject, KeyAndCert signer) + throws Exception { + X509Certificate certificate = createCert(keyPair.getPublic(), signer.privateKey(), + signer.subject(), subject); + KeyAndCert result = new KeyAndCert(keyPair, certificate); + + return result; + } + + public KeyPair createKeyPair() throws Exception { + return createKeyPair(1024); + } + + public KeyPair createKeyPair(int keySize) throws Exception { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keySize, new SecureRandom()); + return gen.generateKeyPair(); + } + + public X509Certificate createCert(KeyPair keyPair, String ca) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(keyPair.getPublic(), ca, ca); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.keyCertSign)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(true)); + + return signCert(builder, keyPair.getPrivate()); + } + + public X509Certificate createCert(PublicKey publicKey, PrivateKey privateKey, + String issuer, String subject) throws Exception { + JcaX509v3CertificateBuilder builder = certBuilder(publicKey, issuer, subject); + builder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature)); + builder.addExtension(Extension.basicConstraints, false, + new BasicConstraints(false)); + + GeneralName[] names = new GeneralName[] { + new GeneralName(GeneralName.dNSName, "localhost") }; + builder.addExtension(Extension.subjectAlternativeName, false, + GeneralNames.getInstance(new DERSequence(names))); + + return signCert(builder, privateKey); + } + + private JcaX509v3CertificateBuilder certBuilder(PublicKey publicKey, String issuer, + String subject) { + X500Name issuerName = new X500Name(String.format("dc=%s", issuer)); + X500Name subjectName = new X500Name(String.format("dc=%s", subject)); + + long now = System.currentTimeMillis(); + BigInteger serialNum = BigInteger.valueOf(now); + Date notBefore = new Date(now - ONE_DAY); + Date notAfter = new Date(now + TEN_YEARS); + + return new JcaX509v3CertificateBuilder(issuerName, serialNum, notBefore, notAfter, + subjectName, publicKey); + } + + private X509Certificate signCert(JcaX509v3CertificateBuilder builder, + PrivateKey privateKey) throws Exception { + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") + .build(privateKey); + X509CertificateHolder holder = builder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(holder); + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java index c1ef9446..d5ea3f88 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigClientRunner.java @@ -20,41 +20,42 @@ import java.io.File; public class TlsConfigClientRunner extends AppRunner { - public TlsConfigClientRunner(Class appClass, AppRunner server) { - super(appClass); - - property("spring.cloud.config.uri", server.root()); - property("spring.cloud.config.enabled", "true"); - } - - public void enableTls() { - property("spring.cloud.config.tls.enabled", "true"); - } - - public void disableTls() { - property("spring.cloud.config.tls.enabled", "false"); - } - - public void setKeyStore(File keyStore, String keyStorePassword, String keyPassword) { - property("spring.cloud.config.tls.key-store", pathOf(keyStore)); - property("spring.cloud.config.tls.key-store-password", keyStorePassword); - property("spring.cloud.config.tls.key-password", keyPassword); - } - - public void setKeyStore(File keyStore) { - property("spring.cloud.config.tls.key-store", pathOf(keyStore)); - } - - public void setTrustStore(File trustStore, String password) { - property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); - property("spring.cloud.config.tls.trust-store-password", password); - } - - public void setTrustStore(File trustStore) { - property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); - } - - private String pathOf(File file) { - return String.format("file:%s", file.getAbsolutePath()); - } + public TlsConfigClientRunner(Class appClass, AppRunner server) { + super(appClass); + + property("spring.cloud.config.uri", server.root()); + property("spring.cloud.config.enabled", "true"); + } + + public void enableTls() { + property("spring.cloud.config.tls.enabled", "true"); + } + + public void disableTls() { + property("spring.cloud.config.tls.enabled", "false"); + } + + public void setKeyStore(File keyStore, String keyStorePassword, String keyPassword) { + property("spring.cloud.config.tls.key-store", pathOf(keyStore)); + property("spring.cloud.config.tls.key-store-password", keyStorePassword); + property("spring.cloud.config.tls.key-password", keyPassword); + } + + public void setKeyStore(File keyStore) { + property("spring.cloud.config.tls.key-store", pathOf(keyStore)); + } + + public void setTrustStore(File trustStore, String password) { + property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); + property("spring.cloud.config.tls.trust-store-password", password); + } + + public void setTrustStore(File trustStore) { + property("spring.cloud.config.tls.trust-store", pathOf(trustStore)); + } + + private String pathOf(File file) { + return String.format("file:%s", file.getAbsolutePath()); + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java index 8d435240..c585bf8d 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/TlsConfigServerRunner.java @@ -20,32 +20,35 @@ import java.io.File; public class TlsConfigServerRunner extends AppRunner { - public TlsConfigServerRunner(Class appClass) { - super(appClass); - property("spring.profiles.active", "native"); - property("spring.cloud.config.server.native.search-locations", "classpath:/test/config"); - } - - public void enableTls() { - property("server.ssl.enabled", "true"); - property("server.ssl.client-auth", "need"); - } - - public void setKeyStore(File keyStore, String keyStorePassword, String key, String keyPassword) { - property("server.ssl.key-store", pathOf(keyStore)); - property("server.ssl.key-store-type", "PKCS12"); - property("server.ssl.key-store-password", keyStorePassword); - property("server.ssl.key-alias", key); - property("server.ssl.key-password", keyPassword); - } - - public void setTrustStore(File trustStore, String password) { - property("server.ssl.trust-store", pathOf(trustStore)); - property("server.ssl.trust-store-type", "PKCS12"); - property("server.ssl.trust-store-password", password); - } - - private String pathOf(File file) { - return String.format("file:%s", file.getAbsolutePath()); - } + public TlsConfigServerRunner(Class appClass) { + super(appClass); + property("spring.profiles.active", "native"); + property("spring.cloud.config.server.native.search-locations", + "classpath:/test/config"); + } + + public void enableTls() { + property("server.ssl.enabled", "true"); + property("server.ssl.client-auth", "need"); + } + + public void setKeyStore(File keyStore, String keyStorePassword, String key, + String keyPassword) { + property("server.ssl.key-store", pathOf(keyStore)); + property("server.ssl.key-store-type", "PKCS12"); + property("server.ssl.key-store-password", keyStorePassword); + property("server.ssl.key-alias", key); + property("server.ssl.key-password", keyPassword); + } + + public void setTrustStore(File trustStore, String password) { + property("server.ssl.trust-store", pathOf(trustStore)); + property("server.ssl.trust-store-type", "PKCS12"); + property("server.ssl.trust-store-password", password); + } + + private String pathOf(File file) { + return String.format("file:%s", file.getAbsolutePath()); + } + } diff --git a/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties b/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties index c5344796..2f4082ae 100644 --- a/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties +++ b/spring-cloud-config-client-tls-tests/src/test/resources/test/config/application.properties @@ -1 +1 @@ -dumb.key=dumb-value \ No newline at end of file +dumb.key=dumb-value diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java index d6c84bbe..c23617e0 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java @@ -33,6 +33,7 @@ import javax.annotation.PostConstruct; import javax.net.ssl.SSLContext; import org.apache.http.ssl.SSLContextBuilder; + import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -110,9 +111,9 @@ public class ConfigClientProperties { * Discovery properties. */ private Discovery discovery = new Discovery(); - + /** - * TLS properties + * TLS properties. */ private TLS tls = new TLS(); @@ -232,7 +233,7 @@ public class ConfigClientProperties { public void setTls(TLS tls) { this.tls = tls; } - + @PostConstruct public void checkTlsStoreType() { tls.checkStoreType(); @@ -444,37 +445,43 @@ public class ConfigClientProperties { } } - + /** - * TLS properties + * TLS properties. */ public static class TLS { - - private static final String DEFAULT_STORE_TYPE = "PKCS12"; - private static final Map EXTENSION_STORE_TYPES = extTypes(); - - private boolean enabled; + + private static final String DEFAULT_STORE_TYPE = "PKCS12"; + + private static final Map EXTENSION_STORE_TYPES = extTypes(); + + private boolean enabled; private Resource keyStore; - private String keyStoreType; - private String keyStorePassword = ""; - private String keyPassword = ""; - private Resource trustStore; - private String trustStoreType; - private String trustStorePassword = ""; - - private static Map extTypes() { - Map result = new HashMap<>(); + private String keyStoreType; - result.put("p12", "PKCS12"); - result.put("pfx", "PKCS12"); - result.put("jks", "JKS"); + private String keyStorePassword = ""; - return Collections.unmodifiableMap(result); - } - - public boolean isEnabled() { + private String keyPassword = ""; + + private Resource trustStore; + + private String trustStoreType; + + private String trustStorePassword = ""; + + private static Map extTypes() { + Map result = new HashMap<>(); + + result.put("p12", "PKCS12"); + result.put("pfx", "PKCS12"); + result.put("jks", "JKS"); + + return Collections.unmodifiableMap(result); + } + + public boolean isEnabled() { return enabled; } @@ -537,113 +544,120 @@ public class ConfigClientProperties { public void setTrustStorePassword(String trustStorePassword) { this.trustStorePassword = trustStorePassword; } - - public char[] keyStorePassword() { - return keyStorePassword.toCharArray(); - } - - public char[] keyPassword() { - return keyPassword.toCharArray(); - } - + + public char[] keyStorePassword() { + return keyStorePassword.toCharArray(); + } + + public char[] keyPassword() { + return keyPassword.toCharArray(); + } + public char[] trustStorePassword() { - return trustStorePassword.toCharArray(); - } - - public void checkStoreType() { - if (keyStore != null && keyStoreType == null) { - keyStoreType = storeTypeOf(keyStore); - } - if (trustStore != null && trustStoreType == null) { - trustStoreType = storeTypeOf(trustStore); - } - } + return trustStorePassword.toCharArray(); + } - private String storeTypeOf(Resource resource) { - String extension = fileExtensionOf(resource); - String type = EXTENSION_STORE_TYPES.get(extension); + public void checkStoreType() { + if (keyStore != null && keyStoreType == null) { + keyStoreType = storeTypeOf(keyStore); + } + if (trustStore != null && trustStoreType == null) { + trustStoreType = storeTypeOf(trustStore); + } + } - return (type == null) ? DEFAULT_STORE_TYPE : type; - } + private String storeTypeOf(Resource resource) { + String extension = fileExtensionOf(resource); + String type = EXTENSION_STORE_TYPES.get(extension); - private String fileExtensionOf(Resource resource) { - String name = resource.getFilename(); - int index = name.lastIndexOf('.'); + return (type == null) ? DEFAULT_STORE_TYPE : type; + } - return index < 0 ? "" : name.substring(index + 1).toLowerCase(); - } - - public SSLContext createSSLContext() throws GeneralSecurityException, IOException { - SSLContextBuilder builder = new SSLContextBuilder(); - char[] keyPassword = keyPassword(); - KeyStore keyStore = createKeyStore(); + private String fileExtensionOf(Resource resource) { + String name = resource.getFilename(); + int index = name.lastIndexOf('.'); - try { - builder.loadKeyMaterial(keyStore, keyPassword); - } catch (UnrecoverableKeyException e) { - if (keyPassword.length == 0) { - // Retry if empty password, see https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - builder.loadKeyMaterial(keyStore, new char[]{'\0'}); - } else { - throw e; - } - } + return index < 0 ? "" : name.substring(index + 1).toLowerCase(); + } - KeyStore trust = createTrustStore(); - if (trust != null) { - builder.loadTrustMaterial(trust, null); - } + public SSLContext createSSLContext() + throws GeneralSecurityException, IOException { + SSLContextBuilder builder = new SSLContextBuilder(); + char[] keyPassword = keyPassword(); + KeyStore keyStore = createKeyStore(); - return builder.build(); - } + try { + builder.loadKeyMaterial(keyStore, keyPassword); + } + catch (UnrecoverableKeyException e) { + if (keyPassword.length == 0) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + builder.loadKeyMaterial(keyStore, new char[] { '\0' }); + } + else { + throw e; + } + } - private KeyStore createKeyStore() throws GeneralSecurityException, IOException { - if (keyStore == null) { - throw new KeyStoreException("Keystore not specified."); - } - if (!keyStore.exists()) { - throw new KeyStoreException("Keystore not exists: " + keyStore); - } + KeyStore trust = createTrustStore(); + if (trust != null) { + builder.loadTrustMaterial(trust, null); + } - KeyStore result = KeyStore.getInstance(keyStoreType); - char[] keyStorePassword = keyStorePassword(); + return builder.build(); + } - try { - loadKeyStore(result, keyStore, keyStorePassword); - } catch (IOException e) { - // Retry if empty password, see https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - if (keyStorePassword.length == 0) { - loadKeyStore(result, keyStore, new char[]{'\0'}); - } else { - throw e; - } - } + private KeyStore createKeyStore() throws GeneralSecurityException, IOException { + if (keyStore == null) { + throw new KeyStoreException("Keystore not specified."); + } + if (!keyStore.exists()) { + throw new KeyStoreException("Keystore not exists: " + keyStore); + } - return result; - } + KeyStore result = KeyStore.getInstance(keyStoreType); + char[] keyStorePassword = keyStorePassword(); - private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, char[] keyStorePassword) - throws IOException, GeneralSecurityException - { - try (InputStream inputStream = keyStoreResource.getInputStream()) { - keyStore.load(inputStream, keyStorePassword); - } - } + try { + loadKeyStore(result, keyStore, keyStorePassword); + } + catch (IOException e) { + // Retry if empty password, see + // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest + if (keyStorePassword.length == 0) { + loadKeyStore(result, keyStore, new char[] { '\0' }); + } + else { + throw e; + } + } - private KeyStore createTrustStore() throws GeneralSecurityException, IOException { - if (trustStore == null) { - return null; - } - if (!trustStore.exists()) { - throw new KeyStoreException("KeyStore not exists: " + trustStore); - } + return result; + } + + private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, + char[] keyStorePassword) throws IOException, GeneralSecurityException { + try (InputStream inputStream = keyStoreResource.getInputStream()) { + keyStore.load(inputStream, keyStorePassword); + } + } + + private KeyStore createTrustStore() throws GeneralSecurityException, IOException { + if (trustStore == null) { + return null; + } + if (!trustStore.exists()) { + throw new KeyStoreException("KeyStore not exists: " + trustStore); + } + + KeyStore result = KeyStore.getInstance(trustStoreType); + try (InputStream input = trustStore.getInputStream()) { + result.load(input, trustStorePassword()); + } + return result; + } - KeyStore result = KeyStore.getInstance(trustStoreType); - try (InputStream input = trustStore.getInputStream()) { - result.load(input, trustStorePassword()); - } - return result; - } } } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java index 171cc246..ecd1808a 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java @@ -33,6 +33,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClients; + import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginTrackedValue; @@ -308,7 +309,7 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator if (client.getRequestConnectTimeout() < 0) { throw new IllegalStateException("Invalid Value for Connect Timeout set."); } - + ClientHttpRequestFactory requestFactory = createHttpRquestFactory(client); RestTemplate template = new RestTemplate(requestFactory); Map headers = new HashMap<>(client.getHeaders()); @@ -322,28 +323,33 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator return template; } - - private ClientHttpRequestFactory createHttpRquestFactory(ConfigClientProperties client) { + + private ClientHttpRequestFactory createHttpRquestFactory( + ConfigClientProperties client) { if (client.getTls().isEnabled()) { try { - SSLContext sslContext = client.getTls().createSSLContext(); - HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); - HttpComponentsClientHttpRequestFactory result = new HttpComponentsClientHttpRequestFactory(httpClient); - - result.setReadTimeout(client.getRequestReadTimeout()); - result.setConnectTimeout(client.getRequestConnectTimeout()); - return result; - - } catch (GeneralSecurityException | IOException ex) { + SSLContext sslContext = client.getTls().createSSLContext(); + HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext) + .build(); + HttpComponentsClientHttpRequestFactory result = new HttpComponentsClientHttpRequestFactory( + httpClient); + + result.setReadTimeout(client.getRequestReadTimeout()); + result.setConnectTimeout(client.getRequestConnectTimeout()); + return result; + + } + catch (GeneralSecurityException | IOException ex) { logger.error(ex); - throw new IllegalStateException("Failed to create config client with TLS.", ex); + throw new IllegalStateException( + "Failed to create config client with TLS.", ex); } } - + SimpleClientHttpRequestFactory result = new SimpleClientHttpRequestFactory(); - result.setReadTimeout(client.getRequestReadTimeout()); - result.setConnectTimeout(client.getRequestConnectTimeout()); - return result; + result.setReadTimeout(client.getRequestReadTimeout()); + result.setConnectTimeout(client.getRequestConnectTimeout()); + return result; } private void addAuthorizationToken(ConfigClientProperties configClientProperties, From 1cda3189a8f0e362550e1573649a932b3b9207ca Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sun, 26 Jul 2020 20:29:14 +0800 Subject: [PATCH 04/10] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 77d885e7..e0f528a6 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ org.bouncycastle bcpkix-jdk15on ${bouncycastle.version} - + From c563011ae7c3455a36e582fe75dcb3d70831549c Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 1 Aug 2020 21:20:19 +0800 Subject: [PATCH 05/10] add document for client side TLS --- .../main/asciidoc/spring-cloud-config.adoc | 22 +++++++++++++++++++ spring-cloud-config-client-tls-tests/pom.xml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 3ae77fa4..d820582c 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -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: + key-store-type: PKCS12 + key-store-password: + key-password: + trust-store: + trust-store-type: PKCS12 + 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, an empty password is assumed. + If you use another form of security, you might need to <> to the `ConfigServicePropertySourceLocator` (for example, by grabbing it in the bootstrap context and injecting it). ==== Health Indicator diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index 40ab9edd..b1a206bb 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-config - 3.0.0-SNAPSHOT + 2.2.4.BUILD-SNAPSHOT .. From 57df44cec5e9ea890b219e03fc88bb22b96507b8 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 1 Aug 2020 21:27:48 +0800 Subject: [PATCH 06/10] Update pom.xml --- spring-cloud-config-client-tls-tests/pom.xml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index b1a206bb..af12ccf8 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -20,10 +20,6 @@ This project is a Spring configuration client. ]]> - - - 2.4.0-M1 - @@ -40,17 +36,14 @@ org.springframework.boot spring-boot - ${spring.boot.version} org.springframework.boot spring-boot-autoconfigure - ${spring.boot.version} org.springframework.boot spring-boot-starter-logging - ${spring.boot.version} true @@ -77,13 +70,11 @@ org.springframework.boot spring-boot-starter-actuator - ${spring.boot.version} true org.springframework.boot spring-boot-starter-aop - ${spring.boot.version} true @@ -93,13 +84,11 @@ org.springframework.boot spring-boot-autoconfigure-processor - ${spring.boot.version} true org.springframework.boot spring-boot-starter-test - ${spring.boot.version} test From c866e4f00b8e342b777926e108fb6b974add2b08 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Sat, 1 Aug 2020 22:04:41 +0800 Subject: [PATCH 07/10] Update spring-cloud-config.adoc --- docs/src/main/asciidoc/spring-cloud-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index d820582c..574e525e 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -1631,7 +1631,7 @@ spring: 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, an empty password is assumed. +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 <> to the `ConfigServicePropertySourceLocator` (for example, by grabbing it in the bootstrap context and injecting it). From b5fff94b4306bce27431625563e3d0d091d05664 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Mon, 24 Aug 2020 00:35:05 +0800 Subject: [PATCH 08/10] use tls common classes --- spring-cloud-config-client-tls-tests/pom.xml | 2 +- .../config/client/ConfigClientProperties.java | 234 +----------------- .../ConfigServicePropertySourceLocator.java | 4 +- 3 files changed, 9 insertions(+), 231 deletions(-) diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index af12ccf8..569ca1ba 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-config - 2.2.4.BUILD-SNAPSHOT + 2.2.5.BUILD-SNAPSHOT .. diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java index c23617e0..3286de66 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientProperties.java @@ -16,29 +16,19 @@ package org.springframework.cloud.config.client; -import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.UnrecoverableKeyException; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; -import javax.net.ssl.SSLContext; - -import org.apache.http.ssl.SSLContextBuilder; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.configuration.TlsProperties; import org.springframework.core.env.Environment; -import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; @@ -115,7 +105,7 @@ public class ConfigClientProperties { /** * TLS properties. */ - private TLS tls = new TLS(); + private TlsProperties tls = new TlsProperties(); /** * Flag to indicate that failure to connect to the server is fatal (default false). @@ -226,17 +216,17 @@ public class ConfigClientProperties { this.discovery = discovery; } - public TLS getTls() { + public TlsProperties getTls() { return tls; } - public void setTls(TLS tls) { + public void setTls(TlsProperties tls) { this.tls = tls; } @PostConstruct public void checkTlsStoreType() { - tls.checkStoreType(); + tls.postConstruct(); } public boolean isFailFast() { @@ -446,218 +436,4 @@ public class ConfigClientProperties { } - /** - * TLS properties. - */ - public static class TLS { - - private static final String DEFAULT_STORE_TYPE = "PKCS12"; - - private static final Map EXTENSION_STORE_TYPES = extTypes(); - - private boolean enabled; - - private Resource keyStore; - - private String keyStoreType; - - private String keyStorePassword = ""; - - private String keyPassword = ""; - - private Resource trustStore; - - private String trustStoreType; - - private String trustStorePassword = ""; - - private static Map extTypes() { - Map result = new HashMap<>(); - - result.put("p12", "PKCS12"); - result.put("pfx", "PKCS12"); - result.put("jks", "JKS"); - - return Collections.unmodifiableMap(result); - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public Resource getKeyStore() { - return keyStore; - } - - public void setKeyStore(Resource keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getKeyPassword() { - return keyPassword; - } - - public void setKeyPassword(String keyPassword) { - this.keyPassword = keyPassword; - } - - public Resource getTrustStore() { - return trustStore; - } - - public void setTrustStore(Resource trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public char[] keyStorePassword() { - return keyStorePassword.toCharArray(); - } - - public char[] keyPassword() { - return keyPassword.toCharArray(); - } - - public char[] trustStorePassword() { - return trustStorePassword.toCharArray(); - } - - public void checkStoreType() { - if (keyStore != null && keyStoreType == null) { - keyStoreType = storeTypeOf(keyStore); - } - if (trustStore != null && trustStoreType == null) { - trustStoreType = storeTypeOf(trustStore); - } - } - - private String storeTypeOf(Resource resource) { - String extension = fileExtensionOf(resource); - String type = EXTENSION_STORE_TYPES.get(extension); - - return (type == null) ? DEFAULT_STORE_TYPE : type; - } - - private String fileExtensionOf(Resource resource) { - String name = resource.getFilename(); - int index = name.lastIndexOf('.'); - - return index < 0 ? "" : name.substring(index + 1).toLowerCase(); - } - - public SSLContext createSSLContext() - throws GeneralSecurityException, IOException { - SSLContextBuilder builder = new SSLContextBuilder(); - char[] keyPassword = keyPassword(); - KeyStore keyStore = createKeyStore(); - - try { - builder.loadKeyMaterial(keyStore, keyPassword); - } - catch (UnrecoverableKeyException e) { - if (keyPassword.length == 0) { - // Retry if empty password, see - // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - builder.loadKeyMaterial(keyStore, new char[] { '\0' }); - } - else { - throw e; - } - } - - KeyStore trust = createTrustStore(); - if (trust != null) { - builder.loadTrustMaterial(trust, null); - } - - return builder.build(); - } - - private KeyStore createKeyStore() throws GeneralSecurityException, IOException { - if (keyStore == null) { - throw new KeyStoreException("Keystore not specified."); - } - if (!keyStore.exists()) { - throw new KeyStoreException("Keystore not exists: " + keyStore); - } - - KeyStore result = KeyStore.getInstance(keyStoreType); - char[] keyStorePassword = keyStorePassword(); - - try { - loadKeyStore(result, keyStore, keyStorePassword); - } - catch (IOException e) { - // Retry if empty password, see - // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest - if (keyStorePassword.length == 0) { - loadKeyStore(result, keyStore, new char[] { '\0' }); - } - else { - throw e; - } - } - - return result; - } - - private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, - char[] keyStorePassword) throws IOException, GeneralSecurityException { - try (InputStream inputStream = keyStoreResource.getInputStream()) { - keyStore.load(inputStream, keyStorePassword); - } - } - - private KeyStore createTrustStore() throws GeneralSecurityException, IOException { - if (trustStore == null) { - return null; - } - if (!trustStore.exists()) { - throw new KeyStoreException("KeyStore not exists: " + trustStore); - } - - KeyStore result = KeyStore.getInstance(trustStoreType); - try (InputStream input = trustStore.getInputStream()) { - result.load(input, trustStorePassword()); - } - return result; - } - - } - } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java index ecd1808a..29abbdfc 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java @@ -42,6 +42,7 @@ import org.springframework.cloud.bootstrap.support.OriginTrackedCompositePropert import org.springframework.cloud.config.client.ConfigClientProperties.Credentials; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; +import org.springframework.cloud.configuration.SSLContextFactory; import org.springframework.core.annotation.Order; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.MapPropertySource; @@ -328,7 +329,8 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator ConfigClientProperties client) { if (client.getTls().isEnabled()) { try { - SSLContext sslContext = client.getTls().createSSLContext(); + SSLContextFactory factory = new SSLContextFactory(client.getTls()); + SSLContext sslContext = factory.createSSLContext(); HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext) .build(); HttpComponentsClientHttpRequestFactory result = new HttpComponentsClientHttpRequestFactory( From c007a81d134bd1d2d5315aef4cada2caa3d44a80 Mon Sep 17 00:00:00 2001 From: JiaLin Date: Mon, 24 Aug 2020 11:19:14 +0800 Subject: [PATCH 09/10] correct one test case about disable client side tls --- .../springframework/cloud/config/client/ConfigClientTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java index 38f35ee1..02b00d1b 100644 --- a/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java +++ b/spring-cloud-config-client-tls-tests/src/test/java/org/springframework/cloud/config/client/ConfigClientTest.java @@ -68,7 +68,7 @@ public class ConfigClientTest extends BaseCertTest { public void tlsClientCanBeDisabled() { try (TlsConfigClientRunner client = createConfigClient()) { enableTlsClient(client); - client.property("spring.cloud.config.enabled", "false"); + client.property("spring.cloud.config.tls.enabled", "false"); client.start(); assertThat(client.getProperty("dumb.key")).isNull(); } From 088bce1491d1077bf2c5ea909899980c1e8be25e Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Mon, 24 Aug 2020 16:13:06 -0400 Subject: [PATCH 10/10] code review changes --- pom.xml | 6 ------ spring-cloud-config-client-tls-tests/pom.xml | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index b76306f4..2cd2e3ce 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ 2.2.5.BUILD-SNAPSHOT 1.11.52 v1-rev20191010-1.30.3 - 1.64 true true @@ -89,11 +88,6 @@ google-auth-library-oauth2-http 0.15.0 - - org.bouncycastle - bcpkix-jdk15on - ${bouncycastle.version} - diff --git a/spring-cloud-config-client-tls-tests/pom.xml b/spring-cloud-config-client-tls-tests/pom.xml index 569ca1ba..55cce5d2 100644 --- a/spring-cloud-config-client-tls-tests/pom.xml +++ b/spring-cloud-config-client-tls-tests/pom.xml @@ -102,6 +102,12 @@ bcpkix-jdk15on test + + org.bouncycastle + bcpkix-jdk15on + 1.64 + test +