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
This commit is contained in:
Mark Paluch
2016-06-04 21:24:49 +02:00
parent 8bd4f5f4b3
commit 1908919cf1
26 changed files with 773 additions and 81 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ _site/
download/
vault/download
vault/vault
work

View File

@@ -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 &

View File

@@ -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

View File

@@ -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

View File

@@ -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.

45
pom.xml
View File

@@ -22,6 +22,10 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
<httpclient.version>4.5.2</httpclient.version>
<httpcore.version>4.4.4</httpcore.version>
<netty.version>4.1.0.Final</netty.version>
<okhttp.version>2.7.5</okhttp.version>
</properties>
<dependencies>
@@ -54,6 +58,41 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
@@ -66,9 +105,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -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;
}
}

View File

@@ -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<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext
.getBeansOfType(AppIdUserIdMechanism.class);

View File

@@ -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<String, String> read(SecureBackendAccessor secureBackendAccessor, VaultToken vaultToken) {
public VaultClient(VaultProperties properties) {
Assert.notNull(properties, "VaultProperties must not be null");
this.properties = properties;
}
public Map<String, String> 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<VaultResponse> response = this.rest.exchange(url,
HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class,
secureBackendAccessor.variables());
ResponseEntity<VaultResponse> 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<String, String> 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;
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

106
src/test/bash/openssl.cnf Normal file
View File

@@ -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 <https://en.wikipedia.org/wiki/Certificate_signing_request>.
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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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<String, String> secretProperties = vaultClient
.read(generic(vaultProperties, "missing"), createToken());
assertThat(secretProperties).isNull();
vaultClient.read(generic(vaultProperties, "missing"), createToken());
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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<ClientHttpRequestFactory> 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();
}
}
});
}
}
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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