From 1908919cf1b0c61f151bb248651a5bef4807a6b0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Sat, 4 Jun 2016 21:24:49 +0200 Subject: [PATCH] Add SSL trust-store configuration. Spring Cloud Vault Config now supports property-based SSL configuration using spring.cloud.vault.ssl.* properties to enable server certificate validation. Introducing a Vault-specific configuration requires to configure HTTP clients individually and so this change adds configuration support for Apache HTTP Components, Netty and the OkHttp client. Clients are picked configured if they exist on the class path. Fixes gh-7 --- .gitignore | 1 + .travis.yml | 1 + README.adoc | 8 +- docs/src/main/asciidoc/README.adoc | 8 +- .../asciidoc/spring-cloud-vault-config.adoc | 21 ++ pom.xml | 45 +++- .../ClientHttpRequestFactoryFactory.java | 220 ++++++++++++++++++ .../vault/VaultBootstrapConfiguration.java | 16 ++ .../cloud/vault/VaultClient.java | 82 ++++--- .../cloud/vault/VaultProperties.java | 32 ++- src/test/bash/create_certificates.sh | 60 +++++ src/test/bash/env.sh | 7 +- src/test/bash/openssl.cnf | 106 +++++++++ .../AppIdAuthenticationIntegrationTests.java | 13 +- ...AuthenticationMethodsIntegrationTests.java | 9 +- .../CassandraSecretIntegrationTests.java | 5 +- ...RequestFactoryFactoryIntegrationTests.java | 87 +++++++ .../vault/GenericSecretIntegrationTests.java | 14 +- .../vault/MySqlSecretIntegrationTests.java | 5 +- .../PostgreSqlSecretIntegrationTests.java | 4 +- .../cloud/vault/PrepareVaultTests.java | 6 +- .../cloud/vault/TestRestTemplateFactory.java | 87 +++++++ .../cloud/vault/util/Settings.java | 5 +- .../cloud/vault/util/VaultRule.java | 7 +- src/test/resources/bootstrap.yml | 2 + src/test/resources/vault.conf | 3 +- 26 files changed, 773 insertions(+), 81 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java create mode 100755 src/test/bash/create_certificates.sh create mode 100644 src/test/bash/openssl.cnf create mode 100644 src/test/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactoryIntegrationTests.java create mode 100644 src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java diff --git a/.gitignore b/.gitignore index 57c5e02c..2990b124 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ _site/ download/ vault/download vault/vault +work diff --git a/.travis.yml b/.travis.yml index a0cf4ab0..342b20be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ install: - tar xzf download/apache-cassandra-2.2.6-bin.tar.gz - cp -f src/test/resources/cassandra.yaml apache-cassandra-2.2.6/conf - apache-cassandra-2.2.6/bin/cassandra + - src/test/bash/create_certificates.sh - src/test/bash/install_vault.sh - src/test/bash/local_run_vault.sh & diff --git a/README.adoc b/README.adoc index 35e27ab0..7a1c04ad 100644 --- a/README.adoc +++ b/README.adoc @@ -5,7 +5,7 @@ Spring Cloud Vault Config provides client-side support for externalized configur == Features -=== Spring Cloud Vauld Config Client +=== Spring Cloud Vault Config Client Specifically for Spring applications: @@ -162,6 +162,12 @@ spring.cloud.vault: == Building +==== Build requirements for Vault + +Spring Cloud Vault Config requires SSL certificates and a running +Vault instance listening on `localhost:8200`. Certificates and the Vault +setup are scripted, the scripts are located in `src/test/bash`. + :jdkversion: 1.7 === Basic Compile and Test diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 340c670f..2ff8dee4 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -3,7 +3,7 @@ include::intro.adoc[] == Features -=== Spring Cloud Vauld Config Client +=== Spring Cloud Vault Config Client Specifically for Spring applications: @@ -18,6 +18,12 @@ include::quickstart.adoc[] == Building +==== Build requirements for Vault + +Spring Cloud Vault Config requires SSL certificates and a running +Vault instance listening on `localhost:8200`. Certificates and the Vault +setup are scripted, the scripts are located in `src/test/bash`. + include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[] == Contributing diff --git a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc index d8eca96b..b363630f 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault-config.adoc @@ -220,3 +220,24 @@ it cannot connect to the Vault Server. If this is the desired behavior, set the bootstrap configuration property `spring.cloud.vault.failFast=true` and the client will halt with an Exception. + +[[vault-client-ssl]] +== Vault Client SSL configuration + +SSL can be configured declaratively by setting various properties. +You can set either `javax.net.ssl.trustStore` to configure +JVM-wide SSL settings or `spring.cloud.vault.ssl.trust-store` +to set SSL settings only for Spring Cloud Vault Config. + +[source,yaml] +---- +spring.cloud.vault: + ssl: + trust-store: classpath:keystore.jks + trust-store-password: changeit +---- + +Please note that configuring `spring.cloud.vault.ssl.*` can be only +applied when either Apache Http Components, netty or the OkHttp client +is on your class-path. + diff --git a/pom.xml b/pom.xml index 84a140f0..c41bfacd 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,10 @@ UTF-8 1.7 + 4.5.2 + 4.4.4 + 4.1.0.Final + 2.7.5 @@ -54,6 +58,41 @@ spring-boot-starter-jdbc test + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + true + + + commons-logging + commons-logging + + + + + + org.apache.httpcomponents + httpcore + ${httpcore.version} + true + + + + io.netty + netty-all + ${netty.version} + true + + + + com.squareup.okhttp + okhttp + ${okhttp.version} + true + + com.h2database h2 @@ -66,9 +105,9 @@ test - mysql - mysql-connector-java - 5.1.38 + mysql + mysql-connector-java + 5.1.38 test diff --git a/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java b/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java new file mode 100644 index 00000000..1d6f183e --- /dev/null +++ b/src/main/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactory.java @@ -0,0 +1,220 @@ +/* + * 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.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import javax.net.ssl.SSLContext; +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.core.io.Resource; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.http.client.OkHttpClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.util.ClassUtils; +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; + +/** + * Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components, + * OkHttp, Netty and the JDK HTTP client (in that order). This factory configures a + * {@link ClientHttpRequestFactory} depending on the available dependencies. + * + * @author Mark Paluch + */ +@CommonsLog +class ClientHttpRequestFactoryFactory { + + private final static boolean HTTP_COMPONENTS_PRESENT = ClassUtils.isPresent( + "org.apache.http.client.HttpClient", + ClientHttpRequestFactoryFactory.class.getClassLoader()); + + private final static boolean OKHTTP_PRESENT = ClassUtils.isPresent( + "com.squareup.okhttp.OkHttpClient", + ClientHttpRequestFactoryFactory.class.getClassLoader()); + + private final static boolean NETTY_PRESENT = ClassUtils.isPresent( + "io.netty.channel.nio.NioEventLoopGroup", + ClientHttpRequestFactoryFactory.class.getClassLoader()); + + /** + * Creates a {@link ClientHttpRequestFactory} for the given {@link VaultProperties}. + * + * @param vaultProperties must not be {@literal null} + * @return a new {@link ClientHttpRequestFactory}. Lifecycle beans must be initialized + * after obtaining. + */ + public static ClientHttpRequestFactory create(VaultProperties vaultProperties) { + + try { + + if (HTTP_COMPONENTS_PRESENT) { + return usingHttpComponents(vaultProperties); + } + + if (OKHTTP_PRESENT) { + return usingOkHttp(vaultProperties); + } + + if (NETTY_PRESENT) { + return usingNetty(vaultProperties); + } + + } + catch (IOException | GeneralSecurityException e) { + throw new IllegalStateException(e); + } + + if (hasSslConfiguration(vaultProperties)) { + log.warn("VaultProperties has SSL configured but the SSL configuration " + + "must be applied outside the Vault Client to use the JDK HTTP client"); + } + + return new SimpleClientHttpRequestFactory(); + } + + protected static ClientHttpRequestFactory usingHttpComponents( + VaultProperties vaultProperties) + throws GeneralSecurityException, IOException { + + HttpClientBuilder httpClientBuilder = HttpClients.custom(); + + if (hasSslConfiguration(vaultProperties)) { + + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + getSSLContext(vaultProperties.getSsl())); + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + } + + RequestConfig requestConfig = RequestConfig.custom() // + .setConnectTimeout(vaultProperties.getConnectionTimeout()) // + .setSocketTimeout(vaultProperties.getReadTimeout()) // + .build(); + + httpClientBuilder.setDefaultRequestConfig(requestConfig); + + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory( + httpClientBuilder.build()); + + return factory; + } + + protected static ClientHttpRequestFactory usingNetty(VaultProperties vaultProperties) + throws GeneralSecurityException, IOException { + + VaultProperties.Ssl ssl = vaultProperties.getSsl(); + + final Netty4ClientHttpRequestFactory requestFactory = new Netty4ClientHttpRequestFactory(); + + if (hasSslConfiguration(vaultProperties)) { + + SslContext sslContext = SslContextBuilder // + .forClient() // + .trustManager(createTrustManagerFactory(ssl.getTrustStore(), + ssl.getTrustStorePassword())) // + .sslProvider(SslProvider.JDK) // + .build(); + + requestFactory.setSslContext(sslContext); + } + + requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout()); + requestFactory.setReadTimeout(vaultProperties.getReadTimeout()); + + return requestFactory; + } + + protected static ClientHttpRequestFactory usingOkHttp(VaultProperties vaultProperties) + throws GeneralSecurityException, IOException { + + final OkHttpClient okHttpClient = new OkHttpClient(); + + OkHttpClientHttpRequestFactory requestFactory = new OkHttpClientHttpRequestFactory( + okHttpClient) { + + @Override + public void destroy() throws Exception { + + if (okHttpClient.getCache() != null) { + okHttpClient.getCache().close(); + } + + okHttpClient.getDispatcher().getExecutorService().shutdown(); + } + }; + + if (hasSslConfiguration(vaultProperties)) { + okHttpClient.setSslSocketFactory( + getSSLContext(vaultProperties.getSsl()).getSocketFactory()); + } + + requestFactory.setConnectTimeout(vaultProperties.getConnectionTimeout()); + requestFactory.setReadTimeout(vaultProperties.getReadTimeout()); + + return requestFactory; + } + + private static SSLContext getSSLContext(VaultProperties.Ssl ssl) + throws GeneralSecurityException, IOException { + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, createTrustManagerFactory(ssl.getTrustStore(), + ssl.getTrustStorePassword()).getTrustManagers(), null); + + return sslContext; + } + + 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(); + } + + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + return trustManagerFactory; + } + + private static boolean hasSslConfiguration(VaultProperties vaultProperties) { + return vaultProperties.getSsl() != null + && vaultProperties.getSsl().getTrustStore() != null; + } +} diff --git a/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java b/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java index 39ac35fe..f0a94927 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java +++ b/src/main/java/org/springframework/cloud/vault/VaultBootstrapConfiguration.java @@ -19,14 +19,17 @@ package org.springframework.cloud.vault; import java.util.Map; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.web.client.RestTemplate; /** * @author Spencer Gibb @@ -37,10 +40,23 @@ import org.springframework.util.ClassUtils; @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) public class VaultBootstrapConfiguration { + @Bean + @Qualifier("vault-ClientHttpRequestFactory") + public ClientHttpRequestFactory clientHttpRequestFactory(){ + return ClientHttpRequestFactoryFactory.create(vaultProperties()); + } + + @Bean + @Qualifier("vault-RestTemplate") + public RestTemplate restTemplate(){ + return new RestTemplate(clientHttpRequestFactory()); + } + @Bean public VaultClient vaultClient(ApplicationContext applicationContext) { VaultClient vaultClient = new VaultClient(vaultProperties()); + vaultClient.setRest(restTemplate()); Map appIdUserIdMechanisms = applicationContext .getBeansOfType(AppIdUserIdMechanism.class); diff --git a/src/main/java/org/springframework/cloud/vault/VaultClient.java b/src/main/java/org/springframework/cloud/vault/VaultClient.java index 0fe16d85..c71fa657 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultClient.java +++ b/src/main/java/org/springframework/cloud/vault/VaultClient.java @@ -13,37 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; -import static com.sun.org.apache.xalan.internal.xsltc.compiler.sym.error; - +import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.Value; - -import lombok.extern.apachecommons.CommonsLog; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.cloud.vault.VaultProperties.AppIdProperties; import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; +import lombok.Setter; +import lombok.Value; +import lombok.extern.apachecommons.CommonsLog; + /** - * Vault client. This client reads data from Vault secret backends and can authenticate with - * Vault to obtain an access token. + * Vault client. This client reads data from Vault secret backends and can authenticate + * with Vault to obtain an access token. * * @author Spencer Gibb * @author Mark Paluch */ -@RequiredArgsConstructor @CommonsLog public class VaultClient { @@ -56,9 +59,18 @@ public class VaultClient { @Setter private AppIdUserIdMechanism appIdUserIdMechanism; + private ClientHttpRequestFactory clientHttpRequestFactory; private final VaultProperties properties; - public Map read(SecureBackendAccessor secureBackendAccessor, VaultToken vaultToken) { + public VaultClient(VaultProperties properties) { + + Assert.notNull(properties, "VaultProperties must not be null"); + + this.properties = properties; + } + + public Map read(SecureBackendAccessor secureBackendAccessor, + VaultToken vaultToken) { Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be empty!"); Assert.notNull(vaultToken, "VaultToken must not be null!"); @@ -69,23 +81,25 @@ public class VaultClient { Exception error = null; String errorBody = null; - log.info(String.format("Fetching config from server at: %s", url)); + URI uri = this.rest.getUriTemplateHandler().expand(url, secureBackendAccessor.variables()); + log.info(String.format("Fetching config from server at: %s", uri)); + try { - ResponseEntity response = this.rest.exchange(url, - HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class, - secureBackendAccessor.variables()); + ResponseEntity response = this.rest.exchange(uri, + HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class); HttpStatus status = response.getStatusCode(); if (status == HttpStatus.OK) { if (response.getBody().getData() != null) { - return secureBackendAccessor.transformProperties(response.getBody().getData()); + return secureBackendAccessor + .transformProperties(response.getBody().getData()); } } } catch (HttpServerErrorException e) { error = e; - if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders() - .getContentType())) { + if (MediaType.APPLICATION_JSON + .includes(e.getResponseHeaders().getContentType())) { errorBody = e.getResponseBodyAsString(); } } @@ -99,8 +113,11 @@ public class VaultClient { error); } - log.warn(String.format("Could not locate PropertySource: %s" - , (errorBody == null ? error==null ? "key not found" : error.getMessage() : errorBody))); + log.warn( + String.format("Could not locate PropertySource: %s", + (errorBody == null + ? error == null ? "key not found" : error.getMessage() + : errorBody))); return Collections.emptyMap(); } @@ -119,16 +136,20 @@ public class VaultClient { */ public VaultToken createToken() { - if (properties.getAuthentication() == AuthenticationMethod.APPID && appIdUserIdMechanism != null) { + if (properties.getAuthentication() == AuthenticationMethod.APPID + && appIdUserIdMechanism != null) { AppIdProperties appId = properties.getAppId(); - return createTokenUsingAppId(new AppIdTuple(properties.getApplicationName(), appIdUserIdMechanism.createUserId()), appId); + return createTokenUsingAppId(new AppIdTuple(properties.getApplicationName(), + appIdUserIdMechanism.createUserId()), appId); } - throw new UnsupportedOperationException(String.format( - "Cannot create a token for auth method %s", properties.getAuthentication())); + throw new UnsupportedOperationException( + String.format("Cannot create a token for auth method %s", + properties.getAuthentication())); } - private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple, AppIdProperties appId) { + private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple, + AppIdProperties appId) { String url = buildUrl(); Map variables = new HashMap<>(); @@ -151,7 +172,8 @@ public class VaultClient { String token = (String) body.getAuth().get("client_token"); return VaultToken.of(token, body.getLeaseDuration()); - } catch (HttpClientErrorException e) { + } + catch (HttpClientErrorException e) { if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) { throw new IllegalStateException(String.format( @@ -176,7 +198,7 @@ public class VaultClient { } @Value - private static class AppIdTuple{ + private static class AppIdTuple { private String appId; private String userId; } diff --git a/src/main/java/org/springframework/cloud/vault/VaultProperties.java b/src/main/java/org/springframework/cloud/vault/VaultProperties.java index c7ecdecd..860fa94d 100644 --- a/src/main/java/org/springframework/cloud/vault/VaultProperties.java +++ b/src/main/java/org/springframework/cloud/vault/VaultProperties.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import org.hibernate.validator.constraints.NotEmpty; @@ -21,6 +20,7 @@ import org.hibernate.validator.constraints.Range; import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.Data; +import org.springframework.core.io.Resource; /** * @author Spencer Gibb @@ -39,7 +39,7 @@ public class VaultProperties { * Vault server host. */ @NotEmpty - private String host = "127.0.0.1"; + private String host = "localhost"; /** * Vault server port. @@ -50,7 +50,7 @@ public class VaultProperties { /** * Protocol scheme. Can be either "http" or "https". */ - private String scheme = "http"; + private String scheme = "https"; /** * Name of the default backend. @@ -70,6 +70,16 @@ public class VaultProperties { @NotEmpty private String profileSeparator = ","; + /** + * Connection timeout; + */ + private int connectionTimeout = 5000; + + /** + * Read timeout; + */ + private int readTimeout = 15000; + /** * Fail fast if data cannot be obtained from Vault. */ @@ -82,6 +92,8 @@ public class VaultProperties { private AppIdProperties appId = new AppIdProperties(); + private Ssl ssl = new Ssl(); + private MySql mysql = new MySql(); private PostgreSql postgresql = new PostgreSql(); @@ -129,6 +141,20 @@ public class VaultProperties { private String userId = MAC_ADDRESS; } + @Data + public static class Ssl { + + /** + * Trust store that holds SSL certificates. + */ + private Resource trustStore; + + /** + * Password used to access the trust store. + */ + private String trustStorePassword; + } + @Data public static class MySql implements DatabaseSecretProperties { diff --git a/src/test/bash/create_certificates.sh b/src/test/bash/create_certificates.sh new file mode 100755 index 00000000..2181ab41 --- /dev/null +++ b/src/test/bash/create_certificates.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +CA_DIR=work/ca +JKS_FILE=work/keystore.jks +if [[ -d work/ca ]] ; then + rm -Rf ${CA_DIR} +fi + +if [[ -f ${JKS_FILE} ]] ; then + rm -Rf ${JKS_FILE} +fi + +mkdir -p ${CA_DIR}/private ${CA_DIR}/certs ${CA_DIR}/crl ${CA_DIR}/csr ${CA_DIR}/newcerts ${CA_DIR}/intermediate + +echo "[INFO] Generating CA private key" +# Less bits = less secure = faster to generate +openssl genrsa -passout pass:changeit -aes256 -out ${CA_DIR}/private/ca.key.pem 2048 + +chmod 400 ${CA_DIR}/private/ca.key.pem + +echo "[INFO] Generating CA certificate" +openssl req -config ${DIR}/openssl.cnf \ + -key ${CA_DIR}/private/ca.key.pem \ + -new -x509 -days 7300 -sha256 -extensions v3_ca \ + -out ${CA_DIR}/certs/ca.cert.pem \ + -passin pass:changeit \ + -subj "/C=NN/ST=Unknown/L=Unknown/O=spring-cloud-vault-config/CN=CA Certificate" + +echo "[INFO] Prepare CA database" +echo 1000 > ${CA_DIR}/serial +touch ${CA_DIR}/index.txt + +echo "[INFO] Generating server private key" +openssl genrsa -aes256 \ + -passout pass:changeit \ + -out ${CA_DIR}/private/localhost.key.pem 2048 + +openssl rsa -in ${CA_DIR}/private/localhost.key.pem \ + -out ${CA_DIR}/private/localhost.decrypted.key.pem \ + -passin pass:changeit + +chmod 400 ${CA_DIR}/private/localhost.key.pem + +echo "[INFO] Generating server certificate request" +openssl req -config ${DIR}/openssl.cnf \ + -key ${CA_DIR}/private/localhost.key.pem \ + -passin pass:changeit \ + -new -sha256 -out ${CA_DIR}/csr/localhost.csr.pem \ + -subj "/C=NN/ST=Unknown/L=Unknown/O=spring-cloud-vault-config/CN=localhost" + +echo "[INFO] Signing certificate request" +openssl ca -config ${DIR}/openssl.cnf \ + -extensions server_cert -days 375 -notext -md sha256 \ + -passin pass:changeit \ + -batch \ + -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 diff --git a/src/test/bash/env.sh b/src/test/bash/env.sh index 1cf1fb9a..274302d5 100755 --- a/src/test/bash/env.sh +++ b/src/test/bash/env.sh @@ -1,9 +1,12 @@ #!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + ########################################################################### # Vault environment settings. Source this file. # ########################################################################### export VAULT_TOKEN=00000000-0000-0000-0000-000000000000 -export VAULT_ADDR=http://localhost:8200 -export VAULT_SKIP_VERIFY=true +export VAULT_ADDR=https://localhost:8200 +export VAULT_SKIP_VERIFY=false +export VAULT_CAPATH=${DIR}/work/ca/certs/ca.cert.pem diff --git a/src/test/bash/openssl.cnf b/src/test/bash/openssl.cnf new file mode 100644 index 00000000..fdf9064d --- /dev/null +++ b/src/test/bash/openssl.cnf @@ -0,0 +1,106 @@ +[ ca ] +# `man ca` +default_ca = CA_default + +[ CA_default ] +# Directory and file locations. +dir = work/ca +certs = $dir/certs +crl_dir = $dir/crl +new_certs_dir = $dir/newcerts +database = $dir/index.txt +serial = $dir/serial +RANDFILE = $dir/private/.rand + +# The root key and root certificate. +private_key = $dir/private/ca.key.pem +certificate = $dir/certs/ca.cert.pem + +# For certificate revocation lists. +crlnumber = $dir/crlnumber +crl = $dir/crl/ca.crl.pem +crl_extensions = crl_ext +default_crl_days = 30 + +# SHA-1 is deprecated, so use SHA-2 instead. +default_md = sha256 + +name_opt = ca_default +cert_opt = ca_default +default_days = 375 +preserve = no +policy = policy_strict + +[ policy_strict ] +# The root CA should only sign intermediate certificates that match. +# See the POLICY FORMAT section of `man ca`. +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +# Options for the `req` tool (`man req`). +default_bits = 2048 +distinguished_name = req_distinguished_name +string_mask = utf8only + +# SHA-1 is deprecated, so use SHA-2 instead. +default_md = sha256 + +# Extension to add when the -x509 option is used. +x509_extensions = v3_ca + +[ req_distinguished_name ] +# See . +countryName = Country Name (2 letter code) +stateOrProvinceName = State or Province Name +localityName = Locality Name +0.organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name +emailAddress = Email Address + +# Optionally, specify some defaults. +countryName_default = NN +stateOrProvinceName_default = Vault Test +localityName_default = +0.organizationName_default = spring-cloud-vault-config +#organizationalUnitName_default = +#emailAddress_default = info@spring-cloud-vault-config.dummy + +[ v3_ca ] +# Extensions for a typical CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ v3_intermediate_ca ] +# Extensions for a typical intermediate CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ usr_cert ] +# Extensions for client certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, emailProtection + +[ server_cert ] +# Extensions for server certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth \ No newline at end of file diff --git a/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationIntegrationTests.java index 1666772c..d4346542 100644 --- a/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import org.junit.Before; @@ -28,14 +27,10 @@ import org.springframework.cloud.vault.util.Settings; */ public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegrationTests { - private VaultClient vaultClient; - @Before public void setUp() throws Exception { - super.setUp(); - - VaultProperties vaultProperties = Settings.createVaultProperties(); + this.vaultProperties = Settings.createVaultProperties(); AppIdProperties appId = configureAppIdProperties(); vaultProperties.setApplicationName("myapp"); @@ -51,9 +46,9 @@ public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegratio prepare().mapAppId(vaultProperties.getApplicationName()); prepare().mapUserId(vaultProperties.getApplicationName(), userId); - vaultClient = new VaultClient(vaultProperties); - vaultClient.setAppIdUserIdMechanism(userIdMechanism); - + this.vaultClient = new VaultClient(vaultProperties); + this.vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); + this.vaultClient.setAppIdUserIdMechanism(userIdMechanism); } @Override diff --git a/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java index e9cbc33c..1248e475 100644 --- a/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/AppIdAuthenticationMethodsIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import static org.assertj.core.api.Assertions.*; @@ -50,6 +49,9 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat VaultClient vaultClient = new VaultClient( prepareAppIdAuthenticationMethod(AppIdProperties.IP_ADDRESS, "myapp")); + + vaultClient.setRest(TestRestTemplateFactory.create(Settings.createVaultProperties())); + vaultClient.setAppIdUserIdMechanism(new IpAddressUserId()); assertThat(vaultClient.createToken()).isNotNull(); } @@ -60,6 +62,8 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat VaultProperties vaultProperties = prepareAppIdAuthenticationMethod("my-user-id", "myapp"); VaultClient vaultClient = new VaultClient(vaultProperties); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); + vaultClient.setAppIdUserIdMechanism(new StaticUserId(vaultProperties)); assertThat(vaultClient.createToken()).isNotNull(); } @@ -70,6 +74,7 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat VaultProperties vaultProperties = prepareAppIdAuthenticationMethod( AppIdProperties.MAC_ADDRESS, "myapp"); VaultClient vaultClient = new VaultClient(vaultProperties); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); vaultClient.setAppIdUserIdMechanism(new MacAddressUserId(vaultProperties)); assertThat(vaultClient.createToken()).isNotNull(); @@ -86,12 +91,12 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat vaultProperties.setApplicationName("foobar"); VaultClient vaultClient = new VaultClient(vaultProperties); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); vaultClient.setAppIdUserIdMechanism(new MacAddressUserId(vaultProperties)); vaultClient.createToken(); fail("Missing IllegalStateException"); - } private VaultProperties prepareAppIdAuthenticationMethod(String userId, String appId) diff --git a/src/test/java/org/springframework/cloud/vault/CassandraSecretIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/CassandraSecretIntegrationTests.java index 9855b694..4fc872d2 100644 --- a/src/test/java/org/springframework/cloud/vault/CassandraSecretIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/CassandraSecretIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import static org.assertj.core.api.Assertions.*; @@ -25,11 +24,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.cloud.vault.util.CanConnect; import org.springframework.cloud.vault.util.Settings; -import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link VaultClient} using the cassandra secret backend. This test @@ -82,7 +81,7 @@ public class CassandraSecretIntegrationTests extends AbstractIntegrationTests { String.format("%s/roles/%s", cassandra.getBackend(), cassandra.getRole()), Collections.singletonMap("creation_cql", CREATE_USER_AND_GRANT_CQL)); - vaultClient.setRest(new RestTemplate()); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); } @Test diff --git a/src/test/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactoryIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactoryIntegrationTests.java new file mode 100644 index 00000000..5a985b9c --- /dev/null +++ b/src/test/java/org/springframework/cloud/vault/ClientHttpRequestFactoryFactoryIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * 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 static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.Test; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cloud.vault.util.Settings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.http.client.OkHttpClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Integration tests for {@link ClientHttpRequestFactory}. + * + * @author Mark Paluch + */ +public class ClientHttpRequestFactoryFactoryIntegrationTests { + + private VaultProperties vaultProperties = Settings.createVaultProperties(); + private String url = String.format("%s://%s:%d/v1/sys/health?sealedcode=200", + vaultProperties.getScheme(), vaultProperties.getHost(), + vaultProperties.getPort()); + + @Test + public void httpComponentsClientShouldWork() throws Exception { + + ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory + .usingHttpComponents(vaultProperties); + RestTemplate template = new RestTemplate(factory); + + String response = template.getForObject(url, String.class); + + assertThat(factory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + public void nettyClientShouldWork() throws Exception { + + ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory + .usingNetty(vaultProperties); + ((InitializingBean) factory).afterPropertiesSet(); + RestTemplate template = new RestTemplate(factory); + + String response = template.getForObject(url, String.class); + + assertThat(factory).isInstanceOf(Netty4ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + public void okHttpClientShouldWork() throws Exception { + + ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory + .usingOkHttp(vaultProperties); + RestTemplate template = new RestTemplate(factory); + + String response = template.getForObject(url, String.class); + + assertThat(factory).isInstanceOf(OkHttpClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java index 31dcf714..a49effcb 100644 --- a/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/GenericSecretIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import static org.assertj.core.api.Assertions.*; @@ -25,7 +24,6 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; import org.springframework.cloud.vault.util.Settings; -import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link VaultClient} using the generic secret backend. @@ -34,15 +32,15 @@ import org.springframework.web.client.RestTemplate; */ public class GenericSecretIntegrationTests extends AbstractIntegrationTests { - private VaultProperties vaultProperties = Settings.createVaultProperties(); - private VaultClient vaultClient = new VaultClient(vaultProperties); + protected VaultProperties vaultProperties = Settings.createVaultProperties(); + protected VaultClient vaultClient = new VaultClient(vaultProperties); @Before public void setUp() throws Exception { vaultProperties.setFailFast(false); prepare().writeSecret("app-name", (Map) createData()); - vaultClient.setRest(new RestTemplate()); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); } @Test @@ -67,11 +65,7 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests { public void shouldFailOnFailFast() throws Exception { vaultProperties.setFailFast(true); - - Map secretProperties = vaultClient - .read(generic(vaultProperties, "missing"), createToken()); - - assertThat(secretProperties).isNull(); + vaultClient.read(generic(vaultProperties, "missing"), createToken()); } /** diff --git a/src/test/java/org/springframework/cloud/vault/MySqlSecretIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/MySqlSecretIntegrationTests.java index f97564fa..6f5d7117 100644 --- a/src/test/java/org/springframework/cloud/vault/MySqlSecretIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/MySqlSecretIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import static org.assertj.core.api.Assertions.*; @@ -24,11 +23,11 @@ import java.net.InetSocketAddress; import java.util.Collections; import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.cloud.vault.util.CanConnect; import org.springframework.cloud.vault.util.Settings; -import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link VaultClient} using the mysql secret backend. This test @@ -72,7 +71,7 @@ public class MySqlSecretIntegrationTests extends AbstractIntegrationTests { prepare().write(String.format("%s/roles/%s", mySql.getBackend(), mySql.getRole()), Collections.singletonMap("sql", CREATE_USER_AND_GRANT_SQL)); - vaultClient.setRest(new RestTemplate()); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); } @Test diff --git a/src/test/java/org/springframework/cloud/vault/PostgreSqlSecretIntegrationTests.java b/src/test/java/org/springframework/cloud/vault/PostgreSqlSecretIntegrationTests.java index a1c308f6..58eddb99 100644 --- a/src/test/java/org/springframework/cloud/vault/PostgreSqlSecretIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/vault/PostgreSqlSecretIntegrationTests.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import static org.assertj.core.api.Assertions.*; @@ -28,7 +27,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.cloud.vault.util.CanConnect; import org.springframework.cloud.vault.util.Settings; -import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link VaultClient} using the postgresql secret backend. This @@ -78,7 +76,7 @@ public class PostgreSqlSecretIntegrationTests extends AbstractIntegrationTests { postgreSql.getRole()), Collections.singletonMap("sql", CREATE_USER_AND_GRANT_SQL)); - vaultClient.setRest(new RestTemplate()); + vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties)); } @Test diff --git a/src/test/java/org/springframework/cloud/vault/PrepareVaultTests.java b/src/test/java/org/springframework/cloud/vault/PrepareVaultTests.java index def8056d..51d8aed1 100644 --- a/src/test/java/org/springframework/cloud/vault/PrepareVaultTests.java +++ b/src/test/java/org/springframework/cloud/vault/PrepareVaultTests.java @@ -13,13 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault; import org.junit.Test; -import org.springframework.boot.test.TestRestTemplate; -import org.springframework.cloud.vault.VaultProperties; -import org.springframework.cloud.vault.VaultToken; import org.springframework.cloud.vault.util.PrepareVault; import org.springframework.cloud.vault.util.Settings; @@ -31,7 +27,7 @@ import org.springframework.cloud.vault.util.Settings; public class PrepareVaultTests { private VaultProperties vaultProperties = Settings.createVaultProperties(); - private PrepareVault prepareVault = new PrepareVault(new TestRestTemplate()); + private PrepareVault prepareVault = new PrepareVault(TestRestTemplateFactory.create(vaultProperties)); @Test public void initializeShouldCreateANewVault() throws Exception { diff --git a/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java b/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java new file mode 100644 index 00000000..4d7c66bf --- /dev/null +++ b/src/test/java/org/springframework/cloud/vault/TestRestTemplateFactory.java @@ -0,0 +1,87 @@ +/* + * 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.util.concurrent.atomic.AtomicReference; + +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 lombok.SneakyThrows; + +/** + * Factory for {@link TestRestTemplate}. The template caches the + * {@link ClientHttpRequestFactory} once it was initialized. Changes to timeouts or the + * SSL configuration won't be applied once a {@link ClientHttpRequestFactory} was created + * for the first time. + * @author Mark Paluch + */ +public class TestRestTemplateFactory { + + private final static AtomicReference factoryCache = new AtomicReference<>(); + + /** + * @param vaultProperties + * @return + */ + @SneakyThrows + public static TestRestTemplate create(VaultProperties vaultProperties) { + + initializeClientHttpRequestFactory(vaultProperties); + + TestRestTemplate testRestTemplate = new TestRestTemplate(); + testRestTemplate.setRequestFactory(factoryCache.get()); + + return testRestTemplate; + } + + protected static void initializeClientHttpRequestFactory( + VaultProperties vaultProperties) throws Exception { + + if (factoryCache.get() != null) { + return; + } + + final ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory + .create(vaultProperties); + + if (factoryCache.compareAndSet(null, clientHttpRequestFactory)) { + + if (clientHttpRequestFactory instanceof InitializingBean) { + ((InitializingBean) clientHttpRequestFactory).afterPropertiesSet(); + } + + if (clientHttpRequestFactory instanceof DisposableBean) { + + Runtime.getRuntime().addShutdownHook( + new Thread("ClientHttpRequestFactory Shutdown Hook") { + + @Override + public void run() { + try { + ((DisposableBean) clientHttpRequestFactory).destroy(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + } +} diff --git a/src/test/java/org/springframework/cloud/vault/util/Settings.java b/src/test/java/org/springframework/cloud/vault/util/Settings.java index f62db9e9..b8503a95 100644 --- a/src/test/java/org/springframework/cloud/vault/util/Settings.java +++ b/src/test/java/org/springframework/cloud/vault/util/Settings.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault.util; import org.springframework.cloud.vault.VaultProperties; import org.springframework.cloud.vault.VaultToken; +import org.springframework.core.io.FileSystemResource; /** * Utility to retrieve settings during test. @@ -33,8 +33,9 @@ public class Settings { public static VaultProperties createVaultProperties() { VaultProperties vaultProperties = new VaultProperties(); + vaultProperties.getSsl().setTrustStorePassword("changeit"); + vaultProperties.getSsl().setTrustStore(new FileSystemResource("work/keystore.jks")); vaultProperties.setToken(token().getToken()); - vaultProperties.setHost(System.getProperty("vault.host", "localhost")); return vaultProperties; } diff --git a/src/test/java/org/springframework/cloud/vault/util/VaultRule.java b/src/test/java/org/springframework/cloud/vault/util/VaultRule.java index 3c66a92f..7e719c68 100644 --- a/src/test/java/org/springframework/cloud/vault/util/VaultRule.java +++ b/src/test/java/org/springframework/cloud/vault/util/VaultRule.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.cloud.vault.util; import java.net.InetAddress; @@ -21,7 +20,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import org.junit.rules.ExternalResource; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.vault.TestRestTemplateFactory; import org.springframework.cloud.vault.VaultProperties; import org.springframework.cloud.vault.VaultToken; @@ -33,14 +32,16 @@ import org.springframework.cloud.vault.VaultToken; public class VaultRule extends ExternalResource { private final VaultProperties vaultProperties; - private final PrepareVault prepareVault = new PrepareVault(new TestRestTemplate()); + private final PrepareVault prepareVault; public VaultRule() { this(Settings.createVaultProperties()); } public VaultRule(VaultProperties vaultProperties) { + this.vaultProperties = vaultProperties; + this.prepareVault = new PrepareVault(TestRestTemplateFactory.create(vaultProperties)); } @Override diff --git a/src/test/resources/bootstrap.yml b/src/test/resources/bootstrap.yml index 0cafc06b..05b0ca87 100644 --- a/src/test/resources/bootstrap.yml +++ b/src/test/resources/bootstrap.yml @@ -1,3 +1,5 @@ spring: application.name: testVaultApp cloud.vault.token: 00000000-0000-0000-0000-000000000000 + cloud.vault.ssl.trust-store: file:work/keystore.jks + cloud.vault.ssl.trust-store-password: changeit diff --git a/src/test/resources/vault.conf b/src/test/resources/vault.conf index b30cf3ff..bbb4510e 100644 --- a/src/test/resources/vault.conf +++ b/src/test/resources/vault.conf @@ -3,7 +3,8 @@ backend "inmem" { listener "tcp" { address = "0.0.0.0:8200" - tls_disable = 1 + tls_cert_file = "work/ca/certs/localhost.cert.pem" + tls_key_file = "work/ca/private/localhost.decrypted.key.pem" } disable_mlock = true