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:
7
pom.xml
7
pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
* @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;
|
||||
* }
|
||||
* }
|
||||
* </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";
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Annotation support for the Spring Vault.
|
||||
*/
|
||||
package org.springframework.vault.annotation;
|
||||
149
spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java
vendored
Normal file
149
spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java
vendored
Normal 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()]);
|
||||
}
|
||||
}
|
||||
4
spring-vault-core/src/main/java/org/springframework/vault/core/env/package-info.java
vendored
Normal file
4
spring-vault-core/src/main/java/org/springframework/vault/core/env/package-info.java
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Spring Vault's environment abstraction consisting property source support.
|
||||
*/
|
||||
package org.springframework.vault.core.env;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 Spring’s `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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user