diff --git a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc index 420ac7b5..6fdca23a 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc @@ -218,6 +218,37 @@ authentication method See also: https://www.vaultproject.io/docs/auth/aws-ec2.html[Vault Documentation: Using the aws-ec2 auth backend] +=== TLS certificate authentication + +The `cert` auth backend allows authentication using SSL/TLS client +certificates that are either signed by a CA or self-signed. + +To enable `cert` authentication you need to: + +1. Use SSL, see <> +2. Configure a Java `Keystore` that contains the client +certificate and the private key +3. Set the `spring.cloud.vault.authentication` to `CERT` + +[source,yaml] +.bootstrap.yml +---- +spring.cloud.vault: + authentication: CERT + ssl: + key-store: classpath:keystore.jks + key-store-password: changeit + cert-auth-path: cert +---- + +* `key-store` sets the resource for the key-store. SSL-secured Vault +communication will validate the Vault SSL certificate with the specified +trust-store. +* `key-store-password` sets the key-store password +* `cert-auth-path` sets the path of the cert authentication mount to use + +See also: https://www.vaultproject.io/docs/auth/cert.html[Vault Documentation: Using the cert auth backend] + == Backends [[vault-client-generic]] diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigTlsCertAuthenticationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigTlsCertAuthenticationTests.java new file mode 100644 index 00000000..62ed0656 --- /dev/null +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigTlsCertAuthenticationTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.vault.config; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.vault.VaultProperties; +import org.springframework.cloud.vault.util.Settings; +import org.springframework.cloud.vault.util.VaultRule; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.cloud.vault.util.Settings.*; + +import org.assertj.core.util.Files; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Integration test using config infrastructure with TLS certificate authentication. In + * case this test should fail because of SSL make sure you run the test within the + * spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is + * referenced with {@code ../work/keystore.jks}. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = VaultConfigTlsCertAuthenticationTests.TestApplication.class) +@IntegrationTest({ "spring.cloud.vault.authentication=cert", + "spring.cloud.vault.ssl.key-store=file:../work/client-cert.jks", + "spring.cloud.vault.ssl.key-store-password=changeit", + "spring.application.name=VaultConfigTlsCertAuthenticationTests" }) +public class VaultConfigTlsCertAuthenticationTests { + + @BeforeClass + public static void beforeClass() throws Exception { + + VaultRule vaultRule = new VaultRule(); + vaultRule.before(); + + vaultRule.prepare().writeSecret( + VaultConfigTlsCertAuthenticationTests.class.getSimpleName(), + Collections.singletonMap("vault.value", "foo")); + + VaultProperties vaultProperties = Settings.createVaultProperties(); + + if (!vaultRule.prepare().hasAuth(vaultProperties.getSsl().getCertAuthPath())) { + vaultRule.prepare().mountAuth(vaultProperties.getSsl().getCertAuthPath()); + } + + File workDir = findWorkDir(); + + String certificate = Files.contentOf( + new File(workDir, "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII); + + Map role = new HashMap<>(); + role.put("certificate", certificate); + role.put("policies", "root"); + + vaultRule.prepare().write("auth/cert/certs/my-role", role); + } + + @Value("${vault.value}") + String configValue; + + @Test + public void contextLoads() { + + assertThat(configValue).isEqualTo("foo"); + } + + @SpringBootApplication + public static class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + } +} diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java index 2d89c5e7..b54af72e 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java @@ -20,13 +20,13 @@ import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; +import org.springframework.cloud.vault.VaultProperties.Ssl; import org.springframework.core.io.Resource; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -38,11 +38,15 @@ import org.springframework.util.StringUtils; import com.squareup.okhttp.OkHttpClient; -import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import lombok.extern.apachecommons.CommonsLog; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; + /** * Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components, * OkHttp, Netty and the JDK HTTP client (in that order). This factory configures a @@ -104,25 +108,48 @@ class ClientHttpRequestFactoryFactory { private static SSLContext getSSLContext(VaultProperties.Ssl ssl) throws GeneralSecurityException, IOException { + KeyManager[] keyManagers = ssl.getKeyStore() != null ? createKeyManagerFactory( + ssl.getKeyStore(), ssl.getKeyStorePassword()).getKeyManagers() : null; + + TrustManager[] trustManagers = ssl.getTrustStore() != null ? createTrustManagerFactory( + ssl.getTrustStore(), ssl.getTrustStorePassword()).getTrustManagers() + : null; + SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, createTrustManagerFactory(ssl.getTrustStore(), - ssl.getTrustStorePassword()).getTrustManagers(), null); + sslContext.init(keyManagers, trustManagers, null); return sslContext; } + private static KeyManagerFactory createKeyManagerFactory(Resource keystoreFile, + String storePassword) throws GeneralSecurityException, IOException { + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + + try (InputStream inputStream = keystoreFile.getInputStream()) { + keyStore.load(inputStream, + StringUtils.hasText(storePassword) ? storePassword.toCharArray() + : null); + } + + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, + StringUtils.hasText(storePassword) ? storePassword.toCharArray() + : new char[0]); + + return keyManagerFactory; + } + private static TrustManagerFactory createTrustManagerFactory(Resource trustFile, String storePassword) throws GeneralSecurityException, IOException { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - InputStream inputStream = trustFile.getInputStream(); - try { - trustStore.load(inputStream, StringUtils.hasText(storePassword) - ? storePassword.toCharArray() : null); - } - finally { - inputStream.close(); + try (InputStream inputStream = trustFile.getInputStream()) { + trustStore.load(inputStream, + StringUtils.hasText(storePassword) ? storePassword.toCharArray() + : null); } TrustManagerFactory trustManagerFactory = TrustManagerFactory @@ -133,8 +160,14 @@ class ClientHttpRequestFactoryFactory { } private static boolean hasSslConfiguration(VaultProperties vaultProperties) { - return vaultProperties.getSsl() != null - && vaultProperties.getSsl().getTrustStore() != null; + + Ssl ssl = vaultProperties.getSsl(); + + if (ssl == null) { + return false; + } + + return ssl.getTrustStore() != null || ssl.getKeyStore() != null; } /** @@ -143,30 +176,31 @@ class ClientHttpRequestFactoryFactory { * @author Mark Paluch */ static class HttpComponents { - protected static ClientHttpRequestFactory usingHttpComponents( - VaultProperties vaultProperties) - throws GeneralSecurityException, IOException { + + static ClientHttpRequestFactory usingHttpComponents( + VaultProperties vaultProperties) throws GeneralSecurityException, + IOException { HttpClientBuilder httpClientBuilder = HttpClients.custom(); if (hasSslConfiguration(vaultProperties)) { + SSLContext sslContext = getSSLContext(vaultProperties.getSsl()); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( - getSSLContext(vaultProperties.getSsl())); + sslContext); httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + httpClientBuilder.setSSLContext(sslContext); } RequestConfig requestConfig = RequestConfig.custom() // .setConnectTimeout(vaultProperties.getConnectionTimeout()) // .setSocketTimeout(vaultProperties.getReadTimeout()) // + .setAuthenticationEnabled(true) // .build(); httpClientBuilder.setDefaultRequestConfig(requestConfig); - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory( - httpClientBuilder.build()); - - return factory; + return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build()); } } @@ -177,8 +211,7 @@ class ClientHttpRequestFactoryFactory { */ static class OkHttp { - protected static ClientHttpRequestFactory usingOkHttp( - VaultProperties vaultProperties) + static ClientHttpRequestFactory usingOkHttp(VaultProperties vaultProperties) throws GeneralSecurityException, IOException { final OkHttpClient okHttpClient = new OkHttpClient(); @@ -198,8 +231,8 @@ class ClientHttpRequestFactoryFactory { }; if (hasSslConfiguration(vaultProperties)) { - okHttpClient.setSslSocketFactory( - getSSLContext(vaultProperties.getSsl()).getSocketFactory()); + okHttpClient.setSslSocketFactory(getSSLContext(vaultProperties.getSsl()) + .getSocketFactory()); } requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout()); @@ -215,8 +248,8 @@ class ClientHttpRequestFactoryFactory { * @author Mark Paluch */ static class Netty { - protected static ClientHttpRequestFactory usingNetty( - VaultProperties vaultProperties) + + static ClientHttpRequestFactory usingNetty(VaultProperties vaultProperties) throws GeneralSecurityException, IOException { VaultProperties.Ssl ssl = vaultProperties.getSsl(); @@ -225,14 +258,21 @@ class ClientHttpRequestFactoryFactory { if (hasSslConfiguration(vaultProperties)) { - SslContext sslContext = SslContextBuilder // - .forClient() // - .trustManager(createTrustManagerFactory(ssl.getTrustStore(), - ssl.getTrustStorePassword())) // - .sslProvider(SslProvider.JDK) // - .build(); + SslContextBuilder sslContextBuilder = SslContextBuilder // + .forClient(); - requestFactory.setSslContext(sslContext); + if (ssl.getTrustStore() != null) { + sslContextBuilder.trustManager(createTrustManagerFactory( + ssl.getTrustStore(), ssl.getTrustStorePassword())); + } + + if (ssl.getKeyStore() != null) { + sslContextBuilder.keyManager(createKeyManagerFactory( + ssl.getKeyStore(), ssl.getKeyStorePassword())); + } + + requestFactory.setSslContext(sslContextBuilder.sslProvider( + SslProvider.JDK).build()); } requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout()); diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java index 71aed1b4..b6955c81 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/DefaultClientAuthentication.java @@ -16,11 +16,13 @@ package org.springframework.cloud.vault; import java.net.URI; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; -import org.springframework.http.HttpStatus; +import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod; +import org.springframework.cloud.vault.VaultProperties.Ssl; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; @@ -90,6 +92,13 @@ class DefaultClientAuthentication extends ClientAuthentication { appIdUserIdMechanism.createUserId()), appId); } + if (properties.getAuthentication() == AuthenticationMethod.CERT + && properties.getSsl() != null) { + log.info("Using TLS Certificate authentication to log into Vault"); + + return createTokenUsingTlsCertAuthentication(properties.getSsl()); + } + if (properties.getAuthentication() == VaultProperties.AuthenticationMethod.AWS_EC2) { log.info("Using AWS-EC2 authentication to log into Vault"); @@ -104,14 +113,13 @@ class DefaultClientAuthentication extends ClientAuthentication { private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple, VaultProperties.AppIdProperties appId) { - URI uri = vaultClient.buildUri(properties, + URI uri = VaultClient.buildUri(properties, String.format("auth/%s/login", appId.getAppIdPath())); Map login = getAppIdLogin(appIdTuple); VaultClientResponse response = vaultClient.write(uri, login); - HttpStatus status = response.getStatusCode(); if (!response.isSuccessful()) { throw new IllegalStateException(String.format( "Cannot login using app-id: %s", response.getMessage())); @@ -125,6 +133,26 @@ class DefaultClientAuthentication extends ClientAuthentication { return VaultToken.of(token, body.getLeaseDuration()); } + private VaultToken createTokenUsingTlsCertAuthentication(Ssl ssl) { + + URI uri = VaultClient.buildUri(properties, + String.format("auth/%s/login", ssl.getCertAuthPath())); + + VaultClientResponse response = vaultClient.write(uri, Collections.emptyMap()); + + if (!response.isSuccessful()) { + throw new IllegalStateException(String.format( + "Cannot login using TLS certificates: %s", response.getMessage())); + } + + VaultResponse body = response.getBody(); + String token = (String) body.getAuth().get("client_token"); + + log.debug("Login successful using TLS certificates"); + + return VaultToken.of(token, body.getLeaseDuration()); + } + private Map getAppIdLogin(AppIdTuple appIdTuple) { Map login = new HashMap<>(); @@ -138,7 +166,7 @@ class DefaultClientAuthentication extends ClientAuthentication { VaultProperties.AwsEc2Properties awsEc2 = this.properties.getAwsEc2(); - URI uri = vaultClient.buildUri(this.properties, + URI uri = VaultClient.buildUri(this.properties, String.format("auth/%s/login", awsEc2.getAwsEc2Path())); Map login = getEc2Login(awsEc2); diff --git a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultProperties.java b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultProperties.java index 2e8c3ef0..5f885dc0 100644 --- a/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultProperties.java +++ b/spring-cloud-vault-core/src/main/java/org/springframework/cloud/vault/VaultProperties.java @@ -150,6 +150,16 @@ public class VaultProperties { @Data public static class Ssl { + /** + * Trust store that holds certificates and private keys. + */ + private Resource keyStore; + + /** + * Password used to access the key store. + */ + private String keyStorePassword; + /** * Trust store that holds SSL certificates. */ @@ -159,9 +169,15 @@ public class VaultProperties { * Password used to access the trust store. */ private String trustStorePassword; + + /** + * Mount path of the TLS cert authentication backend. + */ + @NotEmpty + private String certAuthPath = "cert"; } public enum AuthenticationMethod { - TOKEN, APPID, AWS_EC2, + TOKEN, APPID, AWS_EC2, CERT } } diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/CertAuthenticationMethodsIntegrationTests.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/CertAuthenticationMethodsIntegrationTests.java new file mode 100644 index 00000000..03b3278d --- /dev/null +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/CertAuthenticationMethodsIntegrationTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.vault; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.cloud.vault.ClientHttpRequestFactoryFactory.Netty; +import org.springframework.cloud.vault.ClientHttpRequestFactoryFactory.OkHttp; +import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod; +import org.springframework.cloud.vault.util.Settings; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.client.ClientHttpRequestFactory; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.cloud.vault.util.Settings.*; + +import org.assertj.core.util.Files; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * Integration tests for {@link VaultClient} using TLS certificate authentication using + * various HTTP clients. + * + * @author Mark Paluch + */ +public class CertAuthenticationMethodsIntegrationTests extends AbstractIntegrationTests { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + VaultProperties vaultProperties = prepareCertAuthenticationMethod(); + + @Before + public void setUp() throws Exception { + + if (!prepare().hasAuth("cert")) { + prepare().mountAuth("cert"); + } + + File workDir = findWorkDir(); + + String certificate = Files.contentOf( + new File(workDir, "ca/certs/client.cert.pem"), StandardCharsets.US_ASCII); + + prepare().write("auth/cert/certs/my-role", + Collections.singletonMap("certificate", certificate)); + } + + @Test + public void shouldAuthenticateUsingCertificateAuthenticationUsingHttpComponents() + throws Exception { + + VaultClient client = new VaultClient( + TestRestTemplateFactory + .create(ClientHttpRequestFactoryFactory.HttpComponents + .usingHttpComponents(vaultProperties))); + + ClientAuthentication clientAuthentication = new DefaultClientAuthentication( + vaultProperties, client); + + assertThat(clientAuthentication.login()).isNotNull(); + } + + @Test + public void shouldAuthenticateUsingCertificateAuthenticationUsingOkHttp() + throws Exception { + + ClientHttpRequestFactory factory = OkHttp.usingOkHttp(vaultProperties); + + VaultClient client = new VaultClient(TestRestTemplateFactory.create(factory)); + + ClientAuthentication clientAuthentication = new DefaultClientAuthentication( + vaultProperties, client); + + assertThat(clientAuthentication.login()).isNotNull(); + + ((DisposableBean) factory).destroy(); + } + + @Test + public void shouldAuthenticateUsingCertificateAuthenticationUsingNetty() + throws Exception { + + ClientHttpRequestFactory factory = Netty.usingNetty(vaultProperties); + + VaultClient client = new VaultClient(TestRestTemplateFactory.create(factory)); + + ClientAuthentication clientAuthentication = new DefaultClientAuthentication( + vaultProperties, client); + + assertThat(clientAuthentication.login()).isNotNull(); + + ((DisposableBean) factory).destroy(); + } + + private VaultProperties prepareCertAuthenticationMethod() { + + VaultProperties vaultProperties = Settings.createVaultProperties(); + + vaultProperties.setAuthentication(AuthenticationMethod.CERT); + + vaultProperties.getSsl().setKeyStorePassword("changeit"); + vaultProperties.getSsl().setKeyStore( + new FileSystemResource(new File(findWorkDir(), "client-cert.jks"))); + + return vaultProperties; + } +} diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java index b7841e6c..bb291187 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.test.TestRestTemplate; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.Assert; import org.springframework.web.client.DefaultResponseErrorHandler; import lombok.SneakyThrows; @@ -37,23 +38,47 @@ public class TestRestTemplateFactory { private final static AtomicReference factoryCache = new AtomicReference<>(); /** - * @param vaultProperties + * Create a new {@link TestRestTemplate} using the {@link VaultProperties}. The + * underlying {@link ClientHttpRequestFactory} is cached. See + * {@link #create(ClientHttpRequestFactory)} to create {@link TestRestTemplate} for a + * given {@link ClientHttpRequestFactory}. + * + * @param vaultProperties must not be {@literal null}. * @return */ @SneakyThrows public static TestRestTemplate create(VaultProperties vaultProperties) { + Assert.notNull(vaultProperties, "VaultProperties must not be null!"); + initializeClientHttpRequestFactory(vaultProperties); + return create(factoryCache.get()); + } + + /** + * Create a new {@link TestRestTemplate} using the {@link ClientHttpRequestFactory}. + * The {@link TestRestTemplate} will throw + * {@link org.springframework.web.client.HttpStatusCodeException exceptions} in error + * cases and behave in that aspect like the regular + * {@link org.springframework.web.client.RestTemplate}. + * + * @param requestFactory must not be {@literal null}. + * @return + */ + @SneakyThrows + public static TestRestTemplate create(ClientHttpRequestFactory requestFactory) { + + Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null!"); TestRestTemplate testRestTemplate = new TestRestTemplate(); testRestTemplate.setErrorHandler(new DefaultResponseErrorHandler()); - testRestTemplate.setRequestFactory(factoryCache.get()); + testRestTemplate.setRequestFactory(requestFactory); return testRestTemplate; } - private static void initializeClientHttpRequestFactory( - VaultProperties vaultProperties) throws Exception { + private static void initializeClientHttpRequestFactory(VaultProperties vaultProperties) + throws Exception { if (factoryCache.get() != null) { return; diff --git a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/Settings.java b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/Settings.java index de3093e1..aa4a8a51 100644 --- a/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/Settings.java +++ b/spring-cloud-vault-core/src/test/java/org/springframework/cloud/vault/util/Settings.java @@ -33,20 +33,38 @@ public class Settings { */ public static VaultProperties createVaultProperties() { - File workDir = findWorkDir(new File(System.getProperty("user.dir"))); + File workDir = findWorkDir(); VaultProperties vaultProperties = new VaultProperties(); vaultProperties.getSsl().setTrustStorePassword("changeit"); - vaultProperties.getSsl().setTrustStore(new FileSystemResource(new File(workDir, "keystore.jks"))); + vaultProperties.getSsl().setTrustStore( + new FileSystemResource(new File(workDir, "keystore.jks"))); vaultProperties.setToken(token().getToken()); return vaultProperties; } - private static File findWorkDir(File file) { + /** + * Find the {@code work} directory, starting at the {@code user.dir} directory. Search + * is performed by walking the parent directories. + * @return the {@link File} pointing to the {@code work} directory + * @throws IllegalStateException If the {@code work} directory cannot be found. + */ + public static File findWorkDir() { + return findWorkDir(new File(System.getProperty("user.dir"))); + } - File searchLevel = file; - while (searchLevel.getParentFile() != null && searchLevel.getParentFile() != searchLevel) { + /** + * Find the {@code work} directory, starting at the given {@code directory}. Search + * is performed by walking the parent directories. + * @return the {@link File} pointing to the {@code work} directory + * @throws IllegalStateException If the {@code work} directory cannot be found. + */ + public static File findWorkDir(File directory) { + + File searchLevel = directory; + while (searchLevel.getParentFile() != null + && searchLevel.getParentFile() != searchLevel) { File work = new File(searchLevel, "work"); if (work.isDirectory() && work.exists()) { @@ -56,14 +74,16 @@ public class Settings { searchLevel = searchLevel.getParentFile(); } - throw new IllegalStateException( - String.format("Cannot find work directory in %s or any parent directories", file.getAbsoluteFile())); + throw new IllegalStateException(String.format( + "Cannot find work directory in %s or any parent directories", + directory.getAbsoluteFile())); } /** * @return the token to use during tests. */ public static VaultToken token() { - return VaultToken.of(System.getProperty("vault.token", "00000000-0000-0000-0000-000000000000")); + return VaultToken.of(System.getProperty("vault.token", + "00000000-0000-0000-0000-000000000000")); } } diff --git a/src/test/bash/create_certificates.sh b/src/test/bash/create_certificates.sh index 2181ab41..a93a55af 100755 --- a/src/test/bash/create_certificates.sh +++ b/src/test/bash/create_certificates.sh @@ -2,13 +2,18 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" CA_DIR=work/ca -JKS_FILE=work/keystore.jks +KEYSTORE_FILE=work/keystore.jks +CLIENT_CERT_KEYSTORE=work/client-cert.jks if [[ -d work/ca ]] ; then rm -Rf ${CA_DIR} fi -if [[ -f ${JKS_FILE} ]] ; then - rm -Rf ${JKS_FILE} +if [[ -f ${KEYSTORE_FILE} ]] ; then + rm -Rf ${KEYSTORE_FILE} +fi + +if [[ -f ${CLIENT_CERT_KEYSTORE} ]] ; then + rm -Rf ${CLIENT_CERT_KEYSTORE} fi mkdir -p ${CA_DIR}/private ${CA_DIR}/certs ${CA_DIR}/crl ${CA_DIR}/csr ${CA_DIR}/newcerts ${CA_DIR}/intermediate @@ -57,4 +62,42 @@ openssl ca -config ${DIR}/openssl.cnf \ -in ${CA_DIR}/csr/localhost.csr.pem \ -out ${CA_DIR}/certs/localhost.cert.pem -${JAVA_HOME}/bin/keytool -importcert -keystore ${JKS_FILE} -file ${CA_DIR}/certs/ca.cert.pem -noprompt -storepass changeit + +echo "[INFO] Generating client auth private key" +openssl genrsa -aes256 \ + -passout pass:changeit \ + -out ${CA_DIR}/private/client.key.pem 2048 + +openssl rsa -in ${CA_DIR}/private/client.key.pem \ + -out ${CA_DIR}/private/client.decrypted.key.pem \ + -passin pass:changeit + +chmod 400 ${CA_DIR}/private/client.key.pem + +echo "[INFO] Generating client certificate request" +openssl req -config ${DIR}/openssl.cnf \ + -key ${CA_DIR}/private/client.key.pem \ + -passin pass:changeit \ + -new -sha256 -out ${CA_DIR}/csr/client.csr.pem \ + -subj "/C=NN/ST=Unknown/L=Unknown/O=spring-cloud-vault-config/CN=client" + +echo "[INFO] Signing certificate request" +openssl ca -config ${DIR}/openssl.cnf \ + -extensions usr_cert -days 375 -notext -md sha256 \ + -passin pass:changeit \ + -batch \ + -in ${CA_DIR}/csr/client.csr.pem \ + -out ${CA_DIR}/certs/client.cert.pem + +echo "[INFO] Creating PKCS12 file with client certificate" +openssl pkcs12 -export -clcerts \ + -in ${CA_DIR}/certs/client.cert.pem \ + -inkey ${CA_DIR}/private/client.decrypted.key.pem \ + -passout pass:changeit \ + -out ${CA_DIR}/client.p12 + +${JAVA_HOME}/bin/keytool -importcert -keystore ${KEYSTORE_FILE} -file ${CA_DIR}/certs/ca.cert.pem -noprompt -storepass changeit +${JAVA_HOME}/bin/keytool -importkeystore \ + -srckeystore ${CA_DIR}/client.p12 -srcstoretype PKCS12 -srcstorepass changeit\ + -destkeystore ${CLIENT_CERT_KEYSTORE} -deststoretype JKS \ + -noprompt -storepass changeit diff --git a/src/test/bash/install_vault.sh b/src/test/bash/install_vault.sh index 56e45f0e..6cb231ed 100755 --- a/src/test/bash/install_vault.sh +++ b/src/test/bash/install_vault.sh @@ -43,4 +43,4 @@ unzip ../download/${VAULT_ZIP} chmod a+x vault # check -./vault --version \ No newline at end of file +./vault --version