Add VaultPropertySource support.

Spring Vault now supports VaultPropertySource. Property sources can be registered programatically to be used with Spring's Property source abstraction. @VaultPropertySource can be declared on @Configuration classes to obtain properties from Vault and expose these properties inside Spring's Environment.

@Configuration
@VaultPropertySource("secret/my-application")
public class AppConfig {

    @Autowired Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setPassword(env.getProperty("database.password"));
        return testBean;
    }
}

Fixes gh-9.
This commit is contained in:
Mark Paluch
2016-09-24 21:56:14 +02:00
parent 22aa877f2f
commit 19ea723649
14 changed files with 802 additions and 2 deletions

View File

@@ -104,6 +104,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.1.0-RC.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>

View File

@@ -81,6 +81,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>

View File

@@ -0,0 +1,88 @@
/*
* 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.vault.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Annotation providing a convenient and declarative mechanism for adding a {@link VaultPropertySource} to Spring's
* {@link org.springframework.core.env.Environment Environment}. To be used in conjunction with @{@link Configuration}
* classes.
* <h3>Example usage</h3>
* <p>
* Given a Vault path {@code secret/my-application} containing the configuration data pair
* {@code database.password=mysecretpassword}, the following {@code @Configuration} class uses
* {@code @VaultPropertySource} to contribute {@code secret/my-application} to the {@code Environment}'s set of
* {@code PropertySources}.
*
* <pre class="code">
* &#064;Configuration
* &#064;VaultPropertySource("secret/my-application")
* public class AppConfig {
* &#064;Autowired Environment env;
*
* &#064;Bean
* public TestBean testBean() {
* TestBean testBean = new TestBean();
* testBean.setPassword(env.getProperty("database.password"));
* return testBean;
* }
* }
* </pre>
*
* Notice that the {@code Environment} object is @{@link org.springframework.beans.factory.annotation.Autowired
* Autowired} into the configuration class and then used when populating the {@code TestBean} object. Given the
* configuration above, a call to {@code testBean.getPassword()} will return "mysecretpassword".
* <p>
* In certain situations, it may not be possible or practical to tightly control property source ordering when using
* {@code @VaultPropertySource} annotations. For example, if the {@code @Configuration} classes above were registered
* via component-scanning, the ordering is difficult to predict. In such cases - and if overriding is important - it is
* recommended that the user fall back to using the programmatic PropertySource API. See
* {@link org.springframework.core.env.ConfigurableEnvironment ConfigurableEnvironment} and
* {@link org.springframework.core.env.MutablePropertySources MutablePropertySources} javadocs for details.
*
* @author Mark Paluch
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(VaultPropertySources.class)
@Import(VaultPropertySourceRegistrar.class)
public @interface VaultPropertySource {
/**
* Indicate the Vault path(s) of the properties to be retrieved. For example, {@code "secret/myapp"} or
* {@code "secret/my-application/profile"}.
* <p>
* Each location will be added to the enclosing {@code Environment} as its own property source, and in the order
* declared.
*/
String[] value();
/**
* Configures the name of the {@link org.springframework.vault.core.VaultTemplate} bean to be used with the property
* sources.
*/
String vaultTemplateRef() default "vaultTemplate";
}

View File

@@ -0,0 +1,137 @@
/*
* 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.vault.annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Registrar to register {@link org.springframework.vault.core.env.VaultPropertySource}s based on
* {@link VaultPropertySource}.
* <p>
* This class registers potentially multiple property sources based on different Vault paths.
* {@link org.springframework.vault.core.env.VaultPropertySource}s are resolved and added to
* {@link ConfigurableEnvironment} once the bean factory is post-processed. This allows injection of Vault properties
* and and lookup using the {@link org.springframework.core.env.Environment}.
*
* @author Mark Paluch
*/
class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ConfigurableEnvironment env = beanFactory.getBean(ConfigurableEnvironment.class);
Map<String, org.springframework.vault.core.env.VaultPropertySource> beans = beanFactory
.getBeansOfType(org.springframework.vault.core.env.VaultPropertySource.class);
MutablePropertySources propertySources = env.getPropertySources();
for (org.springframework.vault.core.env.VaultPropertySource vaultPropertySource : beans.values()) {
if (propertySources.contains(vaultPropertySource.getName())) {
continue;
}
propertySources.addLast(vaultPropertySource);
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
registry.registerBeanDefinition("VaultPropertySourceRegistrar",
BeanDefinitionBuilder //
.rootBeanDefinition(VaultPropertySourceRegistrar.class) //
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE) //
.getBeanDefinition());
Set<AnnotationAttributes> propertySources = attributesForRepeatable(annotationMetadata,
VaultPropertySources.class.getName(), VaultPropertySource.class.getName());
int counter = 0;
for (AnnotationAttributes propertySource : propertySources) {
String[] paths = propertySource.getStringArray("value");
Assert.isTrue(paths.length > 0, "At least one @VaultPropertySource(value) location is required");
String ref = propertySource.getString("vaultTemplateRef");
Assert.hasText(ref, "'vaultTemplateRef' in @EnableVaultPropertySource must not be empty");
for (String propertyPath : paths) {
if (!StringUtils.hasText(propertyPath)) {
continue;
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(org.springframework.vault.core.env.VaultPropertySource.class);
builder.addConstructorArgValue(propertyPath);
builder.addConstructorArgReference(ref);
builder.addConstructorArgValue(propertyPath);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition("vaultPropertySource#" + counter, builder.getBeanDefinition());
counter++;
}
}
}
@SuppressWarnings("unchecked")
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata, String containerClassName,
String annotationClassName) {
Set<AnnotationAttributes> result = new LinkedHashSet<AnnotationAttributes>();
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));
Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
if (container != null && container.containsKey("value")) {
for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
addAttributesIfNotNull(result, containedAttributes);
}
}
return Collections.unmodifiableSet(result);
}
private static void addAttributesIfNotNull(Set<AnnotationAttributes> result, Map<String, Object> attributes) {
if (attributes != null) {
result.add(AnnotationAttributes.fromMap(attributes));
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.vault.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Container annotation that aggregates several {@link VaultPropertySource} annotations.
* <p>
* Can be used natively, declaring several nested {@link VaultPropertySource} annotations. Can also be used in
* conjunction with Java 8's support for <em>repeatable annotations</em>, where {@link VaultPropertySource} can simply
* be declared several times on the same {@linkplain ElementType#TYPE type}, implicitly generating this container
* annotation.
*
* @author Mark Paluch
* @see VaultPropertySource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(VaultPropertySourceRegistrar.class)
public @interface VaultPropertySources {
VaultPropertySource[] value();
}

View File

@@ -0,0 +1,4 @@
/**
* Annotation support for the Spring Vault.
*/
package org.springframework.vault.annotation;

View File

@@ -0,0 +1,149 @@
/*
* 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.vault.core.env;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.vault.client.VaultException;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultResponse;
/**
* {@link PropertySource} that reads keys and values from a {@link VaultTemplate} and {@code path}.
*
* @author Mark Paluch
* @since 3.1
* @see org.springframework.core.env.PropertiesPropertySource
*/
public class VaultPropertySource extends EnumerablePropertySource<VaultTemplate> {
protected final static Logger logger = LoggerFactory.getLogger(VaultPropertySource.class);
private final String path;
private final Map<String, String> properties = new LinkedHashMap<String, String>();
private final Object lock = new Object();
/**
* Create a new {@link VaultPropertySource} given a {@link VaultTemplate} and {@code path} inside of Vault. This
* property source loads properties upon construction.
*
* @param template must not be {@literal null}.
* @param path the path inside Vault (e.g. {@code secret/myapp/myproperties}. Must not be empty or {@literal null}.
*/
public VaultPropertySource(VaultTemplate template, String path) {
this(path, template, path);
}
/**
* Create a new {@link VaultPropertySource} given a {@code name}, {@link VaultTemplate} and {@code path} inside of
* Vault. This property source loads properties upon construction.
*
* @param name name of the property source, must not be {@literal null}.
* @param template must not be {@literal null}.
* @param path the path inside Vault (e.g. {@code secret/myapp/myproperties}. Must not be empty or {@literal null}.
*/
public VaultPropertySource(String name, VaultTemplate template, String path) {
super(name, template);
Assert.hasText(path, "Path name must contain at least one character");
Assert.isTrue(!path.startsWith("/"), "Path name must not start with a slash (/)");
this.path = path;
loadProperties();
}
/**
* Initialize property source and read properties from Vault.
*/
protected void loadProperties() {
synchronized (lock) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Fetching properties from Vault at %s", path));
}
Map<String, String> properties = doGetProperties(path);
if (properties != null) {
this.properties.putAll(properties);
}
}
}
/**
* Hook method to obtain properties from Vault.
*
* @param path the path, must not be empty or {@literal null}.
* @return the resulting {@link Map} or {@literal null} if properties were not found.
* @throws VaultException on problems retrieving properties
*/
protected Map<String, String> doGetProperties(String path) throws VaultException {
VaultResponse vaultResponse = this.source.read(path);
if (vaultResponse == null || vaultResponse.getData() == null) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("No properties found at %s", path));
}
return null;
}
return toStringMap(vaultResponse.getData());
}
/**
* Utility method converting a {@code String/Object} map to a {@code String/String} map.
*
* @param data the map
* @return
*/
protected Map<String, String> toStringMap(Map<String, Object> data) {
Map<String, String> result = new LinkedHashMap<String, String>();
if (data != null) {
for (String s : data.keySet()) {
Object value = data.get(s);
if (value != null) {
result.put(s, value.toString());
}
}
}
return result;
}
@Override
public Object getProperty(String name) {
return this.properties.get(name);
}
@Override
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Spring Vault's environment abstraction consisting property source support.
*/
package org.springframework.vault.core.env;

View File

@@ -0,0 +1,78 @@
/*
* 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.vault.annotation;
import static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.core.VaultIntegrationTestConfiguration;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.util.VaultRule;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Integration test for {@link VaultPropertySource}.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
public class VaultPropertySourceInBeanConfigurationIntegrationTest {
@VaultPropertySource({ "secret/myapp" })
static class Config extends VaultIntegrationTestConfiguration {
@Bean
ClientClass clientClass(@Value("${myapp}") String myapp) {
return new ClientClass(myapp);
}
}
@Autowired ClientClass clientClass;
@BeforeClass
public static void beforeClass() {
VaultRule rule = new VaultRule();
rule.before();
VaultOperations vaultOperations = rule.prepare().getVaultOperations();
vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
}
@Test
public void clientClassShouldContainResolvedProperty() {
assertThat(clientClass.getMyapp()).isEqualTo("myvalue");
}
@Data
@AllArgsConstructor
static class ClientClass {
String myapp;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.vault.annotation;
import static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.core.VaultIntegrationTestConfiguration;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.util.VaultRule;
/**
* Integration test for {@link VaultPropertySource}.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
public class VaultPropertySourceIntegrationTests {
@VaultPropertySource({ "secret/myapp", "secret/myapp/profile" })
static class Config extends VaultIntegrationTestConfiguration {}
@Autowired Environment env;
@Autowired ApplicationContext context;
@Value("${myapp}") String myapp;
@BeforeClass
public static void beforeClass() {
VaultRule rule = new VaultRule();
rule.before();
VaultOperations vaultOperations = rule.prepare().getVaultOperations();
vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
vaultOperations.write("secret/myapp/profile", Collections.singletonMap("myprofile", "myprofilevalue"));
}
@Test
public void environmentShouldResolveProperties() {
assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
}
@Test
public void valueShouldInjectProperty() {
assertThat(myapp).isEqualTo("myvalue");
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.vault.annotation;
import static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.core.VaultIntegrationTestConfiguration;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.util.VaultRule;
/**
* Integration test for {@link VaultPropertySource} using multiple annotations.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
public class VaultPropertySourceMultipleIntegrationTests {
@VaultPropertySources({ @VaultPropertySource("secret/myapp/profile"), @VaultPropertySource("secret/myapp") })
static class Config extends VaultIntegrationTestConfiguration {}
@Autowired Environment env;
@Autowired ApplicationContext context;
@Value("${myapp}") String myapp;
@BeforeClass
public static void beforeClass() {
VaultRule rule = new VaultRule();
rule.before();
VaultOperations vaultOperations = rule.prepare().getVaultOperations();
vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
vaultOperations.write("secret/myapp/profile", Collections.singletonMap("myprofile", "myprofilevalue"));
}
@Test
public void environmentShouldResolveProperties() {
assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
}
@Test
public void valueShouldInjectProperty() {
assertThat(myapp).isEqualTo("myvalue");
}
}

View File

@@ -29,7 +29,7 @@ import org.springframework.vault.util.Settings;
* @author Mark Paluch
*/
@Configuration
class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
public class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
@Override
public VaultEndpoint vaultEndpoint() {

View File

@@ -0,0 +1,84 @@
/*
* 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.vault.core.env;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultResponse;
/**
* Unit tests for {@link VaultPropertySource}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class VaultPropertySourceUnitTests {
@Mock VaultTemplate vaultTemplate;
@Test(expected = IllegalArgumentException.class)
public void shouldRejectEmptyPath() throws Exception {
new VaultPropertySource("hello", vaultTemplate, "");
}
@Test(expected = IllegalArgumentException.class)
public void shouldRejectPathStartingWithSlash() throws Exception {
new VaultPropertySource("hello", vaultTemplate, "/secret");
}
@Test
public void shouldLoadProperties() throws Exception {
prepareResponse();
VaultPropertySource vaultPropertySource = new VaultPropertySource("hello", vaultTemplate, "secret/myapp");
assertThat(vaultPropertySource.getProperty("key")).isEqualTo("value");
assertThat(vaultPropertySource.getProperty("integer")).isEqualTo("1");
}
@Test
public void getPropertyNamesShouldReturnNames() throws Exception {
prepareResponse();
VaultPropertySource vaultPropertySource = new VaultPropertySource("hello", vaultTemplate, "secret/myapp");
assertThat(vaultPropertySource.getPropertyNames()).contains("key", "integer");
}
private void prepareResponse() {
Map<String, Object> data = new LinkedHashMap<String, Object>();
data.put("key", "value");
data.put("integer", 1);
VaultResponse vaultResponse = new VaultResponse();
vaultResponse.setData(data);
when(vaultTemplate.read("secret/myapp")).thenReturn(vaultResponse);
}
}

View File

@@ -253,7 +253,6 @@ SslConfiguration.forTrustStore(new FileSystemResource("keystore.jks"), <2>
SslConfiguration.forKeyStore(new FileSystemResource("keystore.jks"), <3>
"changeit")
----
<1> Full configuration.
<2> Configuring only trust store settings.
@@ -264,6 +263,58 @@ Please note that providing `SslConfiguration` can be only
applied when either Apache Http Components or the OkHttp client
is on your class-path.
[[vault.core.propertysupport]]
== Vault Property Source Support
Vault can be used in many different ways. One specific use-case is using Vault to store encrypted properties. Spring Vault supports Vault as property source to obtain configuration properties using Spring's http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html#beans-property-source-abstraction[PropertySource abstraction].
=== Registering `VaultPropertySource`
Spring Vault provides a `VaultPropertySource` to be used with Vault to obtain properties. It uses the nested `data` element to expose properties stored and encrypted in Vault.
====
[source,java]
----
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new VaultPropertySource(vaultTemplate, "secret/my-application"));
----
====
In the code above, `VaultPropertySource` has been added with highest precedence in the search. If it contains a ´foo` property, it will be detected and returned ahead of any `foo` property in any other `PropertySource`. The `MutablePropertySources` API exposes a number of methods that allow for precise manipulation of the set of property sources.
=== @VaultPropertySource
The `@VaultPropertySource` annotation provides a convenient and declarative mechanism for adding a `PropertySource` to Springs `Environment`.
To be used in conjunction with @Configuration classes.
Example usage
Given a Vault path `secret/my-application` containing the configuration data pair `database.password=mysecretpassword`, the following `@Configuration` class uses `@VaultPropertySource` to contribute `secret/my-application` to the `Environment`'s set of `PropertySources`.
====
[source,java]
----
@Configuration
@VaultPropertySource("secret/my-application")
public class AppConfig {
@Autowired Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setPassword(env.getProperty("database.password"));
return testBean;
}
}
----
====
In certain situations, it may not be possible or practical to tightly control property source ordering when using `@VaultPropertySource` annotations. For example, if the @Configuration classes above were registered via component-scanning, the ordering is difficult to predict. In such cases - and if overriding is important - it is recommended that the user fall back to using the programmatic PropertySource API. See ConfigurableEnvironment and MutablePropertySources javadocs for details.
[[vault.core.executioncallback]]
== Execution callbacks