Split into modules #11

Split Spring Cloud Vault Config into multiple modules: Core, Config, Database/Consul/RabbitMQ integrations.
This commit is contained in:
Mark Paluch
2016-06-21 00:12:43 +02:00
parent c19d2926de
commit c0da914b00
81 changed files with 1912 additions and 839 deletions

View File

@@ -12,7 +12,7 @@ install:
- mkdir -p download
- test -f download/apache-cassandra-2.2.6-bin.tar.gz || wget http://www-eu.apache.org/dist/cassandra/2.2.6/apache-cassandra-2.2.6-bin.tar.gz -O download/apache-cassandra-2.2.6-bin.tar.gz
- tar xzf download/apache-cassandra-2.2.6-bin.tar.gz
- cp -f spring-cloud-vault-config/src/test/resources/cassandra.yaml apache-cassandra-2.2.6/conf
- cp -f spring-cloud-vault-config-databases/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

72
pom.xml
View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
@@ -20,10 +20,74 @@
<description>Configuration Integration with Hashicorp Vault</description>
<modules>
<module>spring-cloud-vault-config</module>
<module>docs</module>
<module>spring-cloud-vault-core</module>
<module>spring-cloud-vault-config</module>
<module>spring-cloud-vault-config-databases</module>
<module>spring-cloud-vault-config-consul</module>
<module>spring-cloud-vault-config-rabbitmq</module>
<module>docs</module>
</modules>
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<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>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-snapshots</id>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-consul</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Cloud Vault Config Consul support</name>
<description>Spring Cloud Vault Config Consul support</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.consul;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
/**
* @author Mark Paluch
*/
@Configuration
@EnableConfigurationProperties
public class VaultConfigConsulBootstrapConfiguration {
@Bean
public SecureBackendAccessorFactory<VaultConsulProperties> secureBackendAccessorFactory() {
return new ConsulSecureBackendAccessorFactory();
}
@Bean
public VaultConsulProperties vaultConsulProperties() {
return new VaultConsulProperties();
}
static class ConsulSecureBackendAccessorFactory
implements SecureBackendAccessorFactory<VaultConsulProperties> {
@Override
public SecureBackendAccessor createSecureBackendAccessor(
VaultConsulProperties properties) {
return forConsul(properties);
}
@Override
public boolean supports(VaultSecretBackend secretBackend) {
return secretBackend instanceof VaultConsulProperties;
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link VaultConsulProperties}. This accessor transforms Vault's token property
* names to names provided with {@link VaultConsulProperties#getTokenProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor forConsul(
final VaultConsulProperties properties) {
Assert.notNull(properties, "VaultConsulProperties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getTokenProperty(), input.get("token"));
return result;
}
};
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.consul;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.vault.VaultSecretBackend;
import lombok.Data;
/**
* Configuration properties for Hashicorp Consul.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.consul")
@Data
public class VaultConsulProperties implements VaultSecretBackend {
/**
* Enable consul backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* Consul backend path.
*/
@NotEmpty
private String backend = "consul";
/**
* Target property for the obtained token.
*/
@NotEmpty
private String tokenProperty = "spring.cloud.consul.token";
}

View File

@@ -0,0 +1,5 @@
/**
* Consul integration with Vault.
* @author Mark Paluch
*/
package org.springframework.cloud.vault.config.consul;

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.config.consul.VaultConfigConsulBootstrapConfiguration

View File

@@ -13,21 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config.consul;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.consul.VaultConfigConsulBootstrapConfiguration.ConsulSecureBackendAccessorFactory.*;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.cloud.vault.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.core.ParameterizedTypeReference;
@@ -35,6 +38,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;
/**
* Integration tests for {@link VaultClient} using the consul secret backend. This test
@@ -58,7 +62,7 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.Consul consul = vaultProperties.getConsul();
private VaultConsulProperties consul = new VaultConsulProperties();
private TestRestTemplate restTemplate = new TestRestTemplate();
@@ -84,8 +88,8 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
HttpEntity<String> requestEntity = new HttpEntity<>(
"{\"Name\": \"sample\", \"Type\": \"management\"}", headers);
ResponseEntity<Map<String, String>> tokenResponse = restTemplate.exchange(
"http://{address}/v1/acl/create", HttpMethod.PUT, requestEntity,
STRING_MAP, CONNECTION_URL);
"http://{host}:{port}/v1/acl/create", HttpMethod.PUT, requestEntity,
STRING_MAP, CONSUL_HOST, CONSUL_PORT);
Map<String, String> consulAccess = new HashMap<>();
consulAccess.put("address", CONNECTION_URL);
@@ -97,7 +101,7 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
prepare().write(
String.format("%s/roles/%s", consul.getBackend(), consul.getRole()),
Collections.singletonMap("policy",
Base64.encodeBase64String(POLICY.getBytes())));
Base64Utils.encodeToString(POLICY.getBytes())));
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
}
@@ -105,7 +109,7 @@ public class ConsulSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(consul(consul),
Map<String, String> secretProperties = vaultClient.read(forConsul(consul),
Settings.token());
assertThat(secretProperties).containsKeys("spring.cloud.consul.token");

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config.consul;
import static org.assertj.core.api.Java6Assertions.*;
import static org.junit.Assume.*;
@@ -25,13 +25,13 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
@@ -42,6 +42,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
@@ -53,10 +54,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultConsulTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigConsulTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.consul.enabled=true",
"spring.cloud.vault.consul.role=readonly" })
public class VaultConsulTests {
public class VaultConfigConsulTests {
private final static String CONSUL_HOST = "localhost";
private final static int CONSUL_PORT = 8500;
@@ -104,7 +105,7 @@ public class VaultConsulTests {
vaultRule.prepare().write("consul/config/access", consulAccess);
vaultRule.prepare().write("consul/roles/readonly", Collections
.singletonMap("policy", Base64.encodeBase64String(POLICY.getBytes())));
.singletonMap("policy", Base64.encode(POLICY.getBytes())));
}
@Value("${spring.cloud.consul.token}")

View File

@@ -0,0 +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

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-databases</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Cloud Vault Config Database support</name>
<description>Spring Cloud Vault Config Database support</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1208.jre7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>2.1.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.databases;
import org.springframework.cloud.vault.VaultSecretBackend;
/**
* Configuration properties interface for database secrets.
*/
public interface DatabaseSecretProperties extends VaultSecretBackend {
/**
* Role name.
*
* @return the role name
*/
String getRole();
/**
* Backend path.
*
* @return the backend path.
*/
String getBackend();
/**
* Name of the target property for the obtained username.
*/
String getUsernameProperty();
/**
* Name of the target property for the obtained password.
*/
String getPasswordProperty();
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.databases;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* Configuration properties for Vault using the Apache Cassandra integration.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.cassandra")
@Data
public class VaultCassandraProperties implements DatabaseSecretProperties {
/**
* Enable cassandra backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* Cassandra backend path.
*/
@NotEmpty
private String backend = "cassandra";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.data.cassandra.username";
/**
* Target property for the obtained password.
*/
@NotEmpty
private String passwordProperty = "spring.data.cassandra.password";
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.databases;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
/**
* @author Mark Paluch
*/
@Configuration
@EnableConfigurationProperties
public class VaultConfigDatabaseBootstrapConfiguration {
@Bean
public SecureBackendAccessorFactory<DatabaseSecretProperties> secureBackendAccessorFactory() {
return new DatabaseSecureBackendAccessorFactory();
}
@Bean
public VaultMySqlProperties mySqlProperties() {
return new VaultMySqlProperties();
}
@Bean
public VaultPostgreSqlProperties postgreSqlProperties() {
return new VaultPostgreSqlProperties();
}
@Bean
public VaultCassandraProperties cassandraProperties() {
return new VaultCassandraProperties();
}
static class DatabaseSecureBackendAccessorFactory
implements SecureBackendAccessorFactory<DatabaseSecretProperties> {
@Override
public SecureBackendAccessor createSecureBackendAccessor(
DatabaseSecretProperties properties) {
return forDatabase(properties);
}
@Override
public boolean supports(VaultSecretBackend secretBackend) {
return secretBackend instanceof DatabaseSecretProperties;
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link DatabaseSecretProperties}. This accessor transforms Vault's
* username/password property names to names provided with
* {@link DatabaseSecretProperties#getUsernameProperty()} and
* {@link DatabaseSecretProperties#getPasswordProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor forDatabase(
final DatabaseSecretProperties properties) {
Assert.notNull(properties, "DatabaseSecretProperties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getUsernameProperty(), input.get("username"));
result.put(properties.getPasswordProperty(), input.get("password"));
return result;
}
};
}
}
}

View File

@@ -0,0 +1,45 @@
package org.springframework.cloud.vault.config.databases;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.vault.VaultSecretBackend;
import lombok.Data;
/**
* Configuration properties for Vault using the MySQL integration.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.mysql")
@Data
public class VaultMySqlProperties implements DatabaseSecretProperties, VaultSecretBackend {
/**
* Enable mysql backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* mysql backend path.
*/
@NotEmpty
private String backend = "mysql";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.datasource.username";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String passwordProperty = "spring.datasource.password";
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.databases;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* Configuration properties for Vault using the PostgreSQL integration.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.postgresql")
@Data
public class VaultPostgreSqlProperties implements DatabaseSecretProperties {
/**
* Enable postgresql backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* postgresql backend path.
*/
@NotEmpty
private String backend = "postgresql";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.datasource.username";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String passwordProperty = "spring.datasource.password";
}

View File

@@ -0,0 +1,5 @@
/**
* Database integration with Vault.
* @author Mark Paluch
*/
package org.springframework.cloud.vault.config.databases;

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration

View File

@@ -13,20 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config.databases;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
import java.net.InetSocketAddress;
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.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
@@ -50,7 +53,7 @@ public class CassandraSecretIntegrationTests extends AbstractIntegrationTests {
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.Cassandra cassandra = vaultProperties.getCassandra();
private VaultCassandraProperties cassandra = new VaultCassandraProperties();
/**
* Initialize cassandra secret backend.
@@ -87,7 +90,7 @@ public class CassandraSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(database(cassandra),
Map<String, String> secretProperties = vaultClient.read(forDatabase(cassandra),
Settings.token());
assertThat(secretProperties).containsKeys("spring.data.cassandra.username",

View File

@@ -13,19 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config.databases;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.forDatabase;
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.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
@@ -46,7 +49,7 @@ public class MySqlSecretIntegrationTests extends AbstractIntegrationTests {
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.MySql mySql = vaultProperties.getMysql();
private VaultMySqlProperties mySql = new VaultMySqlProperties();
/**
* Initialize the mysql secret backend.
@@ -77,7 +80,7 @@ public class MySqlSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(database(mySql),
Map<String, String> secretProperties = vaultClient.read(forDatabase(mySql),
Settings.token());
assertThat(secretProperties).containsKeys("spring.datasource.username",

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;
package org.springframework.cloud.vault.config.databases;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecureBackendAccessorFactory.*;
import java.net.InetSocketAddress;
import java.util.Collections;
@@ -25,6 +25,10 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.vault.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
@@ -49,7 +53,7 @@ public class PostgreSqlSecretIntegrationTests extends AbstractIntegrationTests {
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.PostgreSql postgreSql = vaultProperties.getPostgresql();
private VaultPostgreSqlProperties postgreSql = new VaultPostgreSqlProperties();
/**
* Initialize the postgresql secret backend.
@@ -82,7 +86,7 @@ public class PostgreSqlSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(database(postgreSql),
Map<String, String> secretProperties = vaultClient.read(forDatabase(postgreSql),
Settings.token());
assertThat(secretProperties).containsKeys("spring.datasource.username",

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config.databases;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
@@ -49,10 +49,10 @@ import com.datastax.driver.core.Session;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultCassandraTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigCassandraTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.cassandra.enabled=true",
"spring.cloud.vault.cassandra.role=readonly" })
public class VaultCassandraTests {
public class VaultConfigCassandraTests {
private final static String CASSANDRA_HOST = "localhost";
private final static int CASSANDRA_PORT = 9042;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config.databases;
import static org.junit.Assume.*;
@@ -45,11 +45,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultMySqlTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigMySqlTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.mysql.enabled=true",
"spring.cloud.vault.mysql.role=readonly",
"spring.datasource.url=jdbc:mysql://localhost:3306/mysql?useSSL=false" })
public class VaultMySqlTests {
public class VaultConfigMySqlTests {
private final static int MYSQL_PORT = 3306;
private final static String MYSQL_HOST = "localhost";

View File

@@ -14,29 +14,28 @@
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config.databases;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import javax.sql.DataSource;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.postgresql.jdbc.PgConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -49,10 +48,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultPostgreSqlTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigPostgreSqlTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.postgresql.enabled=true",
"spring.cloud.vault.postgresql.role=readonly", "spring.datasource.url=jdbc:postgresql://localhost:5432/postgres?ssl=false" })
public class VaultPostgreSqlTests {
public class VaultConfigPostgreSqlTests {
private final static String POSTGRES_HOST = "localhost";
private final static int POSTGRES_PORT = 5432;

View File

@@ -0,0 +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

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-rabbitmq</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Cloud Vault Config RabbitMQ support</name>
<description>Spring Cloud Vault Config RabbitMQ support</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.rabbitmq;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
/**
* @author Mark Paluch
*/
@Configuration
@EnableConfigurationProperties
public class VaultConfigRabbitMqBootstrapConfiguration {
@Bean
public SecureBackendAccessorFactory<VaultRabbitMqProperties> secureBackendAccessorFactory() {
return new RabbitMqSecureBackendAccessorFactory();
}
@Bean
public VaultRabbitMqProperties rabbitMqProperties() {
return new VaultRabbitMqProperties();
}
static class RabbitMqSecureBackendAccessorFactory
implements SecureBackendAccessorFactory<VaultRabbitMqProperties> {
@Override
public SecureBackendAccessor createSecureBackendAccessor(
VaultRabbitMqProperties properties) {
return forRabbitMq(properties);
}
@Override
public boolean supports(VaultSecretBackend secretBackend) {
return secretBackend instanceof VaultRabbitMqProperties;
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link VaultRabbitMqProperties}. This accessor transforms Vault's
* username/password property names to names provided with
* {@link VaultRabbitMqProperties#getUsernameProperty()} and
* {@link VaultRabbitMqProperties#getPasswordProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor forRabbitMq(
final VaultRabbitMqProperties properties) {
Assert.notNull(properties, "DatabaseSecretProperties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getUsernameProperty(), input.get("username"));
result.put(properties.getPasswordProperty(), input.get("password"));
return result;
}
};
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config.rabbitmq;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.vault.VaultSecretBackend;
import lombok.Data;
/**
* Configuration properties for Vault using the RabbitMQ integration.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.rabbitmq")
@Data
public class VaultRabbitMqProperties implements VaultSecretBackend {
/**
* Enable rabbitmq backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* RabbitMQ backend path.
*/
@NotEmpty
private String backend = "rabbitmq";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.rabbitmq.username";
/**
* Target property for the obtained password.
*/
@NotEmpty
private String passwordProperty = "spring.rabbitmq.password";
}

View File

@@ -0,0 +1,5 @@
/**
* RabbitMQ integration with Vault.
* @author Mark Paluch
*/
package org.springframework.cloud.vault.config.rabbitmq;

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration

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;
package org.springframework.cloud.vault.config.rabbitmq;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration.RabbitMqSecureBackendAccessorFactory.*;
import java.net.InetSocketAddress;
import java.util.Collections;
@@ -26,6 +26,10 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.vault.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.CanConnect;
import org.springframework.cloud.vault.util.Settings;
@@ -35,7 +39,7 @@ import org.springframework.cloud.vault.util.Settings;
*
* @author Mark Paluch
*/
public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
public class RabbitMqSecretIntegrationTests extends AbstractIntegrationTests {
private final static int RABBITMQ_HTTP_MANAGEMENT_PORT = 15672;
private final static String RABBITMQ_HOST = "localhost";
@@ -43,14 +47,14 @@ public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
private final static String RABBITMQ_USERNAME = "guest";
private final static String RABBITMQ_PASSWORD = "guest";
private final static String RABBITMQ_URI = String
.format("http://%s:%d", RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT);
private final static String RABBITMQ_URI = String.format("http://%s:%d",
RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT);
private final static String VHOSTS_ROLE = "{\"/\":{\"write\": \".*\", \"read\": \".*\"}}";
private VaultProperties vaultProperties = Settings.createVaultProperties();
private VaultClient vaultClient = new VaultClient(vaultProperties);
private VaultProperties.Rabbitmq rabbitmq = vaultProperties.getRabbitmq();
private VaultRabbitMqProperties rabbitmq = new VaultRabbitMqProperties();
/**
* Initialize the mysql secret backend.
@@ -60,7 +64,8 @@ public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
@Before
public void setUp() throws Exception {
assumeTrue(CanConnect.to(new InetSocketAddress(RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT)));
assumeTrue(CanConnect
.to(new InetSocketAddress(RABBITMQ_HOST, RABBITMQ_HTTP_MANAGEMENT_PORT)));
rabbitmq.setEnabled(true);
rabbitmq.setRole("readonly");
@@ -77,7 +82,8 @@ public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
prepare().write(String.format("%s/config/connection", rabbitmq.getBackend()),
connection);
prepare().write(String.format("%s/roles/%s", rabbitmq.getBackend(), rabbitmq.getRole()),
prepare().write(
String.format("%s/roles/%s", rabbitmq.getBackend(), rabbitmq.getRole()),
Collections.singletonMap("vhosts", VHOSTS_ROLE));
vaultClient.setRest(TestRestTemplateFactory.create(vaultProperties));
@@ -86,7 +92,7 @@ public class RabbitMQSecretIntegrationTests extends AbstractIntegrationTests {
@Test
public void shouldCreateCredentialsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient.read(database(rabbitmq),
Map<String, String> secretProperties = vaultClient.read(forRabbitMq(rabbitmq),
Settings.token());
assertThat(secretProperties).containsKeys("spring.rabbitmq.username",

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config.rabbitmq;
import static org.junit.Assume.*;
@@ -49,11 +49,11 @@ import com.rabbitmq.client.ConnectionFactory;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultRabbitMQTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigRabbitMqTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.rabbitmq.enabled=true",
"spring.cloud.vault.rabbitmq.role=readonly",
"spring.rabbitmq.address=localhost" })
public class VaultRabbitMQTests {
public class VaultConfigRabbitMqTests {
private final static int RABBITMQ_HTTP_MANAGEMENT_PORT = 15672;
private final static int RABBITMQ_PORT = 5672;
@@ -133,4 +133,4 @@ public class VaultRabbitMQTests {
SpringApplication.run(TestApplication.class, args);
}
}
}
}

View File

@@ -0,0 +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

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-parent</artifactId>
@@ -15,32 +15,22 @@
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-vault-config</name>
<name>Spring Cloud Vault Configuration Integration</name>
<description>Configuration Integration with Hashicorp Vault</description>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<artifactId>spring-cloud-vault-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -52,99 +42,17 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1208.jre7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>2.1.10</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@@ -1,149 +0,0 @@
/*
* 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.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Collection of common used {@link SecureBackendAccessor accessors} to access secure
* backends.
*
* @author Mark Paluch
*/
public class SecureBackendAccessors {
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
* @param vaultProperties must not be {@literal null}.
* @param key must not be {@literal null} and not empty.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor generic(VaultProperties vaultProperties,
String key) {
Assert.notNull(vaultProperties, "VaultProperties must not be null");
return generic(vaultProperties.getBackend(), key);
}
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
* @param secretBackendPath must not be {@literal null} and not empty.
* @param key must not be {@literal null} and not empty.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor generic(final String secretBackendPath,
final String key) {
Assert.hasText(secretBackendPath, "Secret Backend Path must not be empty");
Assert.hasText(key, "Key must not be empty");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", secretBackendPath);
variables.put("key", key);
return variables;
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
return input;
}
};
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link org.springframework.cloud.vault.VaultProperties.DatabaseSecretProperties}.
* This accessor transforms Vault's username/password property names to names provided
* with {@link VaultProperties.DatabaseSecretProperties#getUsernameProperty()} and
* {@link VaultProperties.DatabaseSecretProperties#getUsernameProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor database(
final VaultProperties.DatabaseSecretProperties properties) {
Assert.notNull(properties, "DatabaseSecretProperties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getUsernameProperty(), input.get("username"));
result.put(properties.getPasswordProperty(), input.get("password"));
return result;
}
};
}
/**
* Creates a {@link SecureBackendAccessor} for a secure backend using
* {@link org.springframework.cloud.vault.VaultProperties.Consul}.
* This accessor transforms Vault's token property names to names provided
* with {@link VaultProperties.Consul#getTokenProperty()}.
*
* @param properties must not be {@literal null}.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor consul(
final VaultProperties.Consul properties) {
Assert.notNull(properties, "Consul properties must not be null");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
Map<String, String> result = new HashMap();
result.put(properties.getTokenProperty(), input.get("token"));
return result;
}
};
}
}

View File

@@ -1,349 +0,0 @@
/*
* Copyright 2013-2015 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 org.hibernate.validator.constraints.NotEmpty;
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
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault")
@Data
public class VaultProperties {
/**
* Enable Vault config server.
*/
private boolean enabled = true;
/**
* Vault server host.
*/
@NotEmpty
private String host = "localhost";
/**
* Vault server port.
*/
@Range(min = 1, max = 65535)
private int port = 8200;
/**
* Protocol scheme. Can be either "http" or "https".
*/
private String scheme = "https";
/**
* Name of the default backend.
*/
@NotEmpty
private String backend = "secret";
/**
* Name of the default context.
*/
@NotEmpty
private String defaultContext = "application";
/**
* Profile-separator to combine application name and profile.
*/
@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.
*/
private boolean failFast = false;
/**
* Static vault token. Required if {@link #authentication} is {@code TOKEN}.
*/
private String token;
private AppIdProperties appId = new AppIdProperties();
private Ssl ssl = new Ssl();
private MySql mysql = new MySql();
private PostgreSql postgresql = new PostgreSql();
private Cassandra cassandra = new Cassandra();
private Consul consul = new Consul();
private Rabbitmq rabbitmq = new Rabbitmq();
/**
* Application name for AppId authentication.
*/
@org.springframework.beans.factory.annotation.Value("${spring.application.name:application}")
private String applicationName;
private AuthenticationMethod authentication = AuthenticationMethod.TOKEN;
@Data
public static class AppIdProperties {
/**
* Property value for UserId generation using a Mac-Address.
* @see MacAddressUserId
*/
public final static String MAC_ADDRESS = "MAC_ADDRESS";
/**
* Property value for UserId generation using an IP-Address.
* @see IpAddressUserId
*/
public final static String IP_ADDRESS = "IP_ADDRESS";
/**
* Mount path of the AppId authentication backend.
*/
private String appIdPath = "app-id";
/**
* Network interface hint for the "MAC_ADDRESS" UserId mechanism.
*/
private String networkInterface = null;
/**
* UserId mechanism. Can be either "MAC_ADDRESS", "IP_ADDRESS", a string or a
* class name.
*/
@NotEmpty
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 {
/**
* Enable mysql backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* mysql backend path.
*/
@NotEmpty
private String backend = "mysql";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.datasource.username";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String passwordProperty = "spring.datasource.password";
}
@Data
public static class PostgreSql implements DatabaseSecretProperties {
/**
* Enable postgresql backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* postgresql backend path.
*/
@NotEmpty
private String backend = "postgresql";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.datasource.username";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String passwordProperty = "spring.datasource.password";
}
@Data
public static class Cassandra implements DatabaseSecretProperties {
/**
* Enable cassandra backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* Cassandra backend path.
*/
@NotEmpty
private String backend = "cassandra";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.data.cassandra.username";
/**
* Target property for the obtained password.
*/
@NotEmpty
private String passwordProperty = "spring.data.cassandra.password";
}
@Data
public static class Consul {
/**
* Enable consul backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* Consul backend path.
*/
@NotEmpty
private String backend = "consul";
/**
* Target property for the obtained token.
*/
@NotEmpty
private String tokenProperty = "spring.cloud.consul.token";
}
@Data
public static class Rabbitmq implements DatabaseSecretProperties {
/**
* Enable rabbitmq backend usage.
*/
private boolean enabled = false;
/**
* Role name for credentials.
*/
private String role;
/**
* RabbitMQ backend path.
*/
@NotEmpty
private String backend = "rabbitmq";
/**
* Target property for the obtained username.
*/
@NotEmpty
private String usernameProperty = "spring.rabbitmq.username";
/**
* Target property for the obtained password.
*/
@NotEmpty
private String passwordProperty = "spring.rabbitmq.password";
}
/**
* Configuration properties interface for database secrets.
*/
public interface DatabaseSecretProperties {
/**
* Role name.
*
* @return the role name
*/
String getRole();
/**
* Backend path.
*
* @return the backend path.
*/
String getBackend();
/**
* Name of the target property for the obtained username.
*/
String getUsernameProperty();
/**
* Name of the target property for the obtained password.
*/
String getPasswordProperty();
}
public enum AuthenticationMethod {
TOKEN, APPID,
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultSecretBackend;
/**
* Factory to convert {@link VaultSecretBackend} instance to a
* {@link SecureBackendAccessor}.
*
* @author Mark Paluch
*/
public interface SecureBackendAccessorFactory<T extends VaultSecretBackend> {
/**
* Converts a {@link VaultSecretBackend} into a {@link SecureBackendAccessor}.
* @param configurationProperties
* @return the {@link SecureBackendAccessor}.
*/
SecureBackendAccessor createSecureBackendAccessor(T configurationProperties);
/**
* Checks whether the {@link VaultSecretBackend} is supported by this
* {@link SecureBackendAccessorFactory}.
* @param secretBackend must not be {@literal null}.
* @return {@literal true} if the given {@link VaultSecretBackend} is supported
*/
boolean supports(VaultSecretBackend secretBackend);
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.util.Assert;
/**
* Collection of common used {@link SecureBackendAccessor accessors} to access secure
* backends.
*
* @author Mark Paluch
*/
class SecureBackendAccessors {
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
* @param vaultProperties must not be {@literal null}.
* @param key must not be {@literal null} and not empty.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor generic(VaultProperties vaultProperties,
String key) {
Assert.notNull(vaultProperties, "VaultProperties must not be null");
return generic(vaultProperties.getBackend(), key);
}
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
* @param secretBackendPath must not be {@literal null} and not empty.
* @param key must not be {@literal null} and not empty.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor generic(final String secretBackendPath,
final String key) {
Assert.hasText(secretBackendPath, "Secret Backend Path must not be empty");
Assert.hasText(key, "Key must not be empty");
return new SecureBackendAccessor() {
@Override
public Map<String, String> variables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", secretBackendPath);
variables.put("key", key);
return variables;
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
return input;
}
};
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.cloud.vault.SecureBackendAccessor;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.cloud.vault.VaultSecretBackend;
/**
* @author Mark Paluch
*/
@CommonsLog
public class SecureBackendFactories {
public static Collection<SecureBackendAccessor> createBackendAcessors(
Collection<VaultSecretBackend> vaultSecretBackends,
Collection<SecureBackendAccessorFactory<? super VaultSecretBackend>> factories) {
List<SecureBackendAccessor> accessors = new ArrayList<>();
for (VaultSecretBackend vaultSecretBackend : vaultSecretBackends) {
if (!vaultSecretBackend.isEnabled()) {
continue;
}
SecureBackendAccessor accessor = createSecureBackendAccessor(factories,
vaultSecretBackend);
if (accessor == null) {
log.warn(String.format("Cannot create SecureBackendAccessor for %s",
vaultSecretBackend));
continue;
}
accessors.add(accessor);
}
return accessors;
}
private static SecureBackendAccessor createSecureBackendAccessor(
Collection<SecureBackendAccessorFactory<? super VaultSecretBackend>> factories,
VaultSecretBackend vaultSecretBackend) {
SecureBackendAccessor accessor = null;
for (SecureBackendAccessorFactory<? super VaultSecretBackend> factory : factories) {
if (factory.supports(vaultSecretBackend)) {
accessor = factory.createSecureBackendAccessor(vaultSecretBackend);
break;
}
}
return accessor;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import java.util.Collection;
import javax.annotation.PostConstruct;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultBootstrapConfiguration;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Mark Paluch
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
@ConditionalOnBean(VaultBootstrapConfiguration.class)
public class VaultConfigBootstrapConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Collection<VaultSecretBackend> vaultSecretBackends;
private Collection<SecureBackendAccessorFactory<? super VaultSecretBackend>> factories;
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator(VaultClient vaultClient,
VaultProperties vaultProperties) {
Collection<SecureBackendAccessor> backendAccessors = SecureBackendFactories
.createBackendAcessors(vaultSecretBackends, factories);
return new VaultPropertySourceLocator(vaultClient, vaultProperties,
backendAccessors);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@PostConstruct
private void postConstruct() {
this.vaultSecretBackends = applicationContext
.getBeansOfType(VaultSecretBackend.class).values();
this.factories = (Collection) applicationContext
.getBeansOfType(SecureBackendAccessorFactory.class).values();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* 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.
@@ -14,18 +14,29 @@
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.cloud.vault.VaultToken;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.Assert;
import lombok.extern.apachecommons.CommonsLog;
/**
* A {@link EnumerablePropertySource} backed by {@link VaultClient}.
*
* @author Spencer Gibb
* @author Mark Paluch
*/
@@ -47,12 +58,13 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
this.vaultState = state;
}
public void init() {
public void init(Collection<SecureBackendAccessor> externalBackendAccessors) {
Assert.hasText(vaultProperties.getBackend(),
"No generic secret backend configured (spring.cloud.vault.backend)");
List<SecureBackendAccessor> accessors = getSecureBackendAccessors();
List<SecureBackendAccessor> accessors = getSecureBackendAccessors(
externalBackendAccessors);
for (SecureBackendAccessor accessor : accessors) {
try {
@@ -80,37 +92,16 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
}
}
private List<SecureBackendAccessor> getSecureBackendAccessors() {
private List<SecureBackendAccessor> getSecureBackendAccessors(
Collection<SecureBackendAccessor> externalBackendAccessors) {
List<SecureBackendAccessor> accessors = new ArrayList<>();
accessors.add(SecureBackendAccessors.generic(vaultProperties.getBackend(),
this.context));
VaultProperties.MySql mySql = vaultProperties.getMysql();
if (mySql.isEnabled()) {
accessors.add(SecureBackendAccessors.database(mySql));
}
accessors.addAll(externalBackendAccessors);
VaultProperties.PostgreSql postgreSql = vaultProperties.getPostgresql();
if (postgreSql.isEnabled()) {
accessors.add(SecureBackendAccessors.database(postgreSql));
}
VaultProperties.Cassandra cassandra = vaultProperties.getCassandra();
if (cassandra.isEnabled()) {
accessors.add(SecureBackendAccessors.database(cassandra));
}
VaultProperties.Consul consul = vaultProperties.getConsul();
if (consul.isEnabled()) {
accessors.add(SecureBackendAccessors.consul(consul));
}
VaultProperties.Rabbitmq rabbitmq = vaultProperties.getRabbitmq();
if (rabbitmq.isEnabled()) {
accessors.add(SecureBackendAccessors.database(rabbitmq));
}
return accessors;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* 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.
@@ -14,38 +14,61 @@
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link PropertySourceLocator} using {@link VaultClient}.
*
* @author Spencer Gibb
* @author Mark Paluch
*/
public class VaultPropertySourceLocator implements PropertySourceLocator {
private VaultClient vault;
private VaultClient vaultClient;
private VaultProperties properties;
private final Collection<SecureBackendAccessor> backendAcessors;
private transient final VaultState vaultState = new VaultState();
public VaultPropertySourceLocator(VaultClient vault, VaultProperties properties) {
this.vault = vault;
/**
* Creates a new {@link VaultPropertySourceLocator}.
* @param vaultClient must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
*/
public VaultPropertySourceLocator(VaultClient vaultClient, VaultProperties properties,
Collection<SecureBackendAccessor> backendAccessors) {
Assert.notNull(vaultClient, "VaultClient must not be null");
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
this.vaultClient = vaultClient;
this.properties = properties;
this.backendAcessors = backendAccessors;
}
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty("spring.application.name");
@@ -66,9 +89,12 @@ public class VaultPropertySourceLocator implements PropertySourceLocator {
CompositePropertySource composite = new CompositePropertySource("vault");
for (String propertySourceContext : contexts) {
VaultPropertySource propertySource = create(propertySourceContext);
propertySource.init();
composite.addPropertySource(propertySource);
if(StringUtils.hasText(propertySourceContext)) {
VaultPropertySource propertySource = create(propertySourceContext);
propertySource.init(backendAcessors);
composite.addPropertySource(propertySource);
}
}
return composite;
@@ -77,11 +103,12 @@ public class VaultPropertySourceLocator implements PropertySourceLocator {
}
private VaultPropertySource create(String context) {
return new VaultPropertySource(context, this.vault, this.properties, this.vaultState);
return new VaultPropertySource(context, this.vaultClient, this.properties,
this.vaultState);
}
private void addProfiles(List<String> contexts, String baseContext,
List<String> profiles) {
List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
}

View File

@@ -14,9 +14,10 @@
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import lombok.Data;
import org.springframework.cloud.vault.VaultToken;
/**
* State of the Vault client.

View File

@@ -1,3 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.VaultBootstrapConfiguration
org.springframework.cloud.vault.config.VaultConfigBootstrapConfiguration

View File

@@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import org.junit.Before;
import org.springframework.cloud.vault.IpAddressUserId;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
import org.springframework.cloud.vault.VaultToken;
import org.springframework.cloud.vault.util.Settings;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;

View File

@@ -13,16 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.cloud.vault.SecureBackendAccessors.*;
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.vault.AbstractIntegrationTests;
import org.springframework.cloud.vault.TestRestTemplateFactory;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.VaultToken;
import org.springframework.cloud.vault.util.Settings;
/**

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.config;
import org.junit.Test;
import org.springframework.cloud.vault.TestRestTemplateFactory;
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;
/**
* Integration tests for {@link PrepareVault}.
*
* @author Mark Paluch
*/
public class PrepareVaultTests {
private VaultProperties vaultProperties = Settings.createVaultProperties();
private PrepareVault prepareVault = new PrepareVault(TestRestTemplateFactory.create(vaultProperties));
@Test
public void initializeShouldCreateANewVault() throws Exception {
prepareVault.setRootToken(Settings.token());
prepareVault.setVaultProperties(vaultProperties);
if (!prepareVault.isAvailable()) {
VaultToken rootToken = prepareVault.initializeVault();
prepareVault.setRootToken(rootToken);
prepareVault.createToken(vaultProperties.getToken(), "root");
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.vault;
package org.springframework.cloud.vault.config;
import static org.assertj.core.api.Assertions.*;
@@ -29,7 +29,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.VaultAppIdCustomMechanismTests.BootstrapConfiguration;
import org.springframework.cloud.vault.AppIdUserIdMechanism;
import org.springframework.cloud.vault.config.VaultConfigAppIdCustomMechanismTests.BootstrapConfiguration;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.context.annotation.Bean;
@@ -41,10 +43,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { BootstrapConfiguration.class,
VaultAppIdCustomMechanismTests.TestApplication.class })
VaultConfigAppIdCustomMechanismTests.TestApplication.class })
@IntegrationTest({ "spring.cloud.vault.authentication=appid", "use.custom.config=true",
"spring.application.name=VaultAppIdCustomMechanismTests" })
public class VaultAppIdCustomMechanismTests {
"spring.application.name=VaultConfigAppIdCustomMechanismTests" })
public class VaultConfigAppIdCustomMechanismTests {
@BeforeClass
public static void beforeClass() throws Exception {
@@ -53,7 +55,7 @@ public class VaultAppIdCustomMechanismTests {
vaultRule.before();
vaultRule.prepare().writeSecret(
VaultAppIdCustomMechanismTests.class.getSimpleName(),
VaultConfigAppIdCustomMechanismTests.class.getSimpleName(),
Collections.singletonMap("vault.value", "foo"));
VaultProperties vaultProperties = Settings.createVaultProperties();
@@ -64,9 +66,9 @@ public class VaultAppIdCustomMechanismTests {
}
vaultRule.prepare()
.mapAppId(VaultAppIdCustomMechanismTests.class.getSimpleName());
.mapAppId(VaultConfigAppIdCustomMechanismTests.class.getSimpleName());
vaultRule.prepare().mapUserId(
VaultAppIdCustomMechanismTests.class.getSimpleName(),
VaultConfigAppIdCustomMechanismTests.class.getSimpleName(),
new StaticUserIdMechanism().createUserId());
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config;
import static org.assertj.core.api.Assertions.*;
@@ -41,11 +41,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultAppIdTests.TestApplication.class)
@SpringApplicationConfiguration(classes = VaultConfigAppIdTests.TestApplication.class)
@IntegrationTest({ "spring.cloud.vault.authentication=appid",
"spring.cloud.vault.app-id.user-id=IP_ADDRESS",
"spring.application.name=VaultAppIdTests" })
public class VaultAppIdTests {
"spring.application.name=VaultConfigAppIdTests" })
public class VaultConfigAppIdTests {
@BeforeClass
public static void beforeClass() throws Exception {
@@ -53,7 +53,7 @@ public class VaultAppIdTests {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
vaultRule.prepare().writeSecret(VaultAppIdTests.class.getSimpleName(),
vaultRule.prepare().writeSecret(VaultConfigAppIdTests.class.getSimpleName(),
Collections.singletonMap("vault.value", "foo"));
VaultProperties vaultProperties = Settings.createVaultProperties();
@@ -64,8 +64,8 @@ public class VaultAppIdTests {
vaultRule.prepare().mountAuth(vaultProperties.getAppId().getAppIdPath());
}
vaultRule.prepare().mapAppId(VaultAppIdTests.class.getSimpleName());
vaultRule.prepare().mapUserId(VaultAppIdTests.class.getSimpleName(),
vaultRule.prepare().mapAppId(VaultConfigAppIdTests.class.getSimpleName());
vaultRule.prepare().mapUserId(VaultConfigAppIdTests.class.getSimpleName(),
new IpAddressUserId().createUserId());
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault.configclient;
package org.springframework.cloud.vault.config;
import static org.assertj.core.api.Assertions.*;
@@ -37,8 +37,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VaultTests.TestApplication.class)
public class VaultTests {
@SpringApplicationConfiguration(classes = VaultConfigTests.TestApplication.class)
public class VaultConfigTests {
@BeforeClass
public static void beforeClass() throws Exception {

View File

@@ -1,3 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.VaultAppIdCustomMechanismTests.BootstrapConfiguration
org.springframework.cloud.vault.config.VaultConfigAppIdCustomMechanismTests.BootstrapConfiguration

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-core</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Cloud Vault Core</name>
<description>Spring Cloud Vault Core</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -77,15 +77,15 @@ class ClientHttpRequestFactoryFactory {
try {
if (HTTP_COMPONENTS_PRESENT) {
return usingHttpComponents(vaultProperties);
return HttpComponents.usingHttpComponents(vaultProperties);
}
if (OKHTTP_PRESENT) {
return usingOkHttp(vaultProperties);
return OkHttp.usingOkHttp(vaultProperties);
}
if (NETTY_PRESENT) {
return usingNetty(vaultProperties);
return Netty.usingNetty(vaultProperties);
}
}
@@ -101,87 +101,6 @@ class ClientHttpRequestFactoryFactory {
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 {
@@ -217,4 +136,109 @@ class ClientHttpRequestFactoryFactory {
return vaultProperties.getSsl() != null
&& vaultProperties.getSsl().getTrustStore() != null;
}
/**
* {@link ClientHttpRequestFactory} for Apache Http Components.
*
* @author Mark Paluch
*/
static class HttpComponents {
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;
}
}
/**
* {@link ClientHttpRequestFactory} for the {@link OkHttpClient}.
*
* @author Mark Paluch
*/
static class OkHttp {
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;
}
}
/**
* {@link ClientHttpRequestFactory} for Netty.
*
* @author Mark Paluch
*/
static class Netty {
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;
}
}
}

View File

@@ -22,16 +22,16 @@ import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import org.springframework.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.util.StringUtils;
/**
* Mechanism to generate a UserId based on the Mac address. {@link MacAddressUserId} creates a hex-encoded
* representation of the Mac address without any separators (0123456789AB). A
* {@link org.springframework.cloud.vault.VaultProperties.AppIdProperties#networkInterface} can be
* {@link VaultProperties.AppIdProperties#networkInterface} can be
* specified optionally to select a network interface (index/name).
*
* @author Mark Paluch

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* 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.
@@ -19,6 +19,8 @@ package org.springframework.cloud.vault;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -41,15 +43,16 @@ import org.springframework.web.client.RestTemplate;
public class VaultBootstrapConfiguration {
@Bean
@Qualifier("vault-ClientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory(){
return ClientHttpRequestFactoryFactory.create(vaultProperties());
public ClientFactoryWrapper clientHttpRequestFactoryWrapper() {
return new ClientFactoryWrapper(
ClientHttpRequestFactoryFactory.create(vaultProperties()));
}
@Bean
@Qualifier("vault-RestTemplate")
public RestTemplate restTemplate(){
return new RestTemplate(clientHttpRequestFactory());
public RestTemplate restTemplate() {
return new RestTemplate(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
}
@Bean
@@ -99,10 +102,34 @@ public class VaultBootstrapConfiguration {
}
}
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator(
ApplicationContext applicationContext) {
return new VaultPropertySourceLocator(vaultClient(applicationContext),
vaultProperties());
/**
* Wrapper for {@link ClientHttpRequestFactory} to not expose the bean globally.
*/
static class ClientFactoryWrapper implements InitializingBean, DisposableBean {
private final ClientHttpRequestFactory clientHttpRequestFactory;
public ClientFactoryWrapper(ClientHttpRequestFactory clientHttpRequestFactory) {
this.clientHttpRequestFactory = clientHttpRequestFactory;
}
@Override
public void destroy() throws Exception {
if (clientHttpRequestFactory instanceof DisposableBean) {
((DisposableBean) clientHttpRequestFactory).destroy();
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (clientHttpRequestFactory instanceof InitializingBean) {
((InitializingBean) clientHttpRequestFactory).afterPropertiesSet();
}
}
public ClientHttpRequestFactory getClientHttpRequestFactory() {
return clientHttpRequestFactory;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* 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.
@@ -57,7 +57,6 @@ public class VaultClient {
@Setter
private AppIdUserIdMechanism appIdUserIdMechanism;
private ClientHttpRequestFactory clientHttpRequestFactory;
private final VaultProperties properties;
public VaultClient(VaultProperties properties) {

View File

@@ -0,0 +1,155 @@
/*
* 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 org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import lombok.Data;
/**
* @author Spencer Gibb
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault")
@Data
public class VaultProperties {
/**
* Enable Vault config server.
*/
private boolean enabled = true;
/**
* Vault server host.
*/
@NotEmpty
private String host = "localhost";
/**
* Vault server port.
*/
@Range(min = 1, max = 65535)
private int port = 8200;
/**
* Protocol scheme. Can be either "http" or "https".
*/
private String scheme = "https";
/**
* Name of the default backend.
*/
@NotEmpty
private String backend = "secret";
/**
* Name of the default context.
*/
@NotEmpty
private String defaultContext = "application";
/**
* Profile-separator to combine application name and profile.
*/
@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.
*/
private boolean failFast = false;
/**
* Static vault token. Required if {@link #authentication} is {@code TOKEN}.
*/
private String token;
private AppIdProperties appId = new AppIdProperties();
private Ssl ssl = new Ssl();
/**
* Application name for AppId authentication.
*/
@org.springframework.beans.factory.annotation.Value("${spring.application.name:application}")
private String applicationName;
private AuthenticationMethod authentication = AuthenticationMethod.TOKEN;
@Data
public static class AppIdProperties {
/**
* Property value for UserId generation using a Mac-Address.
* @see MacAddressUserId
*/
public final static String MAC_ADDRESS = "MAC_ADDRESS";
/**
* Property value for UserId generation using an IP-Address.
* @see IpAddressUserId
*/
public final static String IP_ADDRESS = "IP_ADDRESS";
/**
* Mount path of the AppId authentication backend.
*/
private String appIdPath = "app-id";
/**
* Network interface hint for the "MAC_ADDRESS" UserId mechanism.
*/
private String networkInterface = null;
/**
* UserId mechanism. Can be either "MAC_ADDRESS", "IP_ADDRESS", a string or a
* class name.
*/
@NotEmpty
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;
}
public enum AuthenticationMethod {
TOKEN, APPID,
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* 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.
@@ -18,10 +18,10 @@ package org.springframework.cloud.vault;
import java.util.Map;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* Value object to bind HTTP API responses.
*

View File

@@ -13,17 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.vault;
/**
* A secret backend that can return secrets from Vault.
*
* @author Mark Paluch
*/
public class MyUserIdMechanism implements AppIdUserIdMechanism {
public interface VaultSecretBackend {
@Override
public String createUserId() {
String userId = "";
return userId;
}
/**
* Backend path.
*
* @return the backend path.
*/
String getBackend();
/**
*
* @return {@literal true} if the backend is enabled.
*/
boolean isEnabled();
}

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.vault.VaultBootstrapConfiguration

View File

@@ -41,6 +41,11 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
@Before
public void setUp() throws Exception {
if (!prepare().hasAuth("app-id")) {
prepare().mountAuth("app-id");
}
prepare().mapAppId("myapp");
}
@@ -50,7 +55,8 @@ public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrat
VaultClient vaultClient = new VaultClient(
prepareAppIdAuthenticationMethod(AppIdProperties.IP_ADDRESS, "myapp"));
vaultClient.setRest(TestRestTemplateFactory.create(Settings.createVaultProperties()));
vaultClient.setRest(
TestRestTemplateFactory.create(Settings.createVaultProperties()));
vaultClient.setAppIdUserIdMechanism(new IpAddressUserId());
assertThat(vaultClient.createToken()).isNotNull();

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.vault;
import static org.assertj.core.api.AssertionsForClassTypes.*;
import static org.springframework.cloud.vault.ClientHttpRequestFactoryFactory.*;
import org.junit.Test;
import org.springframework.beans.factory.DisposableBean;
@@ -42,7 +43,7 @@ public class ClientHttpRequestFactoryFactoryIntegrationTests {
@Test
public void httpComponentsClientShouldWork() throws Exception {
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory
ClientHttpRequestFactory factory = HttpComponents
.usingHttpComponents(vaultProperties);
RestTemplate template = new RestTemplate(factory);
@@ -57,7 +58,7 @@ public class ClientHttpRequestFactoryFactoryIntegrationTests {
@Test
public void nettyClientShouldWork() throws Exception {
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory
ClientHttpRequestFactory factory = Netty
.usingNetty(vaultProperties);
((InitializingBean) factory).afterPropertiesSet();
RestTemplate template = new RestTemplate(factory);
@@ -73,7 +74,7 @@ public class ClientHttpRequestFactoryFactoryIntegrationTests {
@Test
public void okHttpClientShouldWork() throws Exception {
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory
ClientHttpRequestFactory factory = OkHttp
.usingOkHttp(vaultProperties);
RestTemplate template = new RestTemplate(factory);

View File

@@ -12,6 +12,6 @@ mkdir -p ${BASEDIR}/consul/data
./consul/consul agent -server \
-bootstrap-expect 1 \
-data-dir ${BASEDIR}/consul/data \
-config-file=${BASEDIR}/spring-cloud-vault-config/src/test/resources/consul.json
-config-file=${BASEDIR}/spring-cloud-vault-core/src/test/resources/consul.json
exit $?

View File

@@ -6,6 +6,6 @@
BASEDIR=`dirname $0`/../../..
./vault/vault server -config=${BASEDIR}/spring-cloud-vault-config/src/test/resources/vault.conf
./vault/vault server -config=${BASEDIR}/spring-cloud-vault-core/src/test/resources/vault.conf
exit $?