Allow configuration of PropertySourceLocator behavior.

We now support configuration of PropertySourceLocator behavior of generic and discovered secret backends. We introduced VaultConfigurer as strategy interface to be implemented by customizer beans in the bootstrap context. VaultConfigurer allows configuration of secret backends via SecretBackendConfigurer.

Motivation: Customization was only possible by implementing an own PropertySourceLocator by extending VaultPropertySourceLocatorSupport and implementing doCreatePropertySources. Both is non-trivial and does not allow reuse of existing functionality.

See gh-116.
This commit is contained in:
Mark Paluch
2017-05-21 17:25:14 +02:00
parent 19da2154c1
commit 2fe14c2a7b
17 changed files with 841 additions and 145 deletions

View File

@@ -750,6 +750,45 @@ spring.cloud.vault:
See also: https://www.vaultproject.io/docs/secrets/postgresql/index.html[Vault Documentation: Setting up PostgreSQL with Vault]
[[vault.config.backends.configurer]]
== Configure `PropertySourceLocator` behavior
Spring Cloud Vault uses property-based configuration to create ``PropertySource``s
for generic and discovered secret backends.
Discovered backends provide `VaultSecretBackendDescriptor` beans to describe the configuration
state to use secret backend as `PropertySource`. A `SecretBackendMetadataFactory` is required
to create a `SecretBackendMetadata` object which contains path, name and property transformation
configuration.
`SecretBackendMetadata` is used to back a particular `PropertySource`.
You can register an arbitrary number of beans implementing `VaultConfigurer` for customization.
Default generic and discovered backend registration is disabled if Spring Cloud Vault discovers
at least one `VaultConfigurer` bean. You can however enable default registration with
`SecretBackendConfigurer.registerDefaultGenericSecretBackends()` and `SecretBackendConfigurer.registerDefaultDiscoveredSecretBackends()`.
====
[source,java]
----
public class CustomizationBean implements VaultConfigurer {
@Override
public void addSecretBackends(SecretBackendConfigurer configurer) {
configurer.add("secret/my-application");
configurer.registerDefaultGenericSecretBackends(false);
configurer.registerDefaultDiscoveredSecretBackends(true);
}
}
----
====
NOTE: All customization is required to happen in the bootstrap context. Add your configuration
classes to `META-INF/spring.factories` at `org.springframework.cloud.bootstrap.BootstrapConfiguration`
in your application.
[[vault.config.fail-fast]]
== Vault Client Fail Fast

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2017 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.util.Assert;
import org.springframework.vault.core.util.PropertyTransformer;
import org.springframework.vault.core.util.PropertyTransformers;
/**
* Default {@link SecretBackendConfigurer} implementation that exposes its configuration
* through {@link PropertySourceLocatorConfiguration}.
*
* @author Mark Paluch
*/
class DefaultSecretBackendConfigurer
implements SecretBackendConfigurer, PropertySourceLocatorConfiguration {
private final Map<String, SecretBackendMetadata> secretBackends = new LinkedHashMap<>();
private boolean registerDefaultGenericSecretBackends = false;
private boolean registerDefaultDiscoveredSecretBackends = false;
@Override
public SecretBackendConfigurer add(String path) {
Assert.hasLength(path, "Path must not be empty");
return add(path, PropertyTransformers.noop());
}
@Override
public SecretBackendConfigurer add(String path,
PropertyTransformer propertyTransformer) {
Assert.hasLength(path, "Path must not be empty");
Assert.notNull(propertyTransformer, "PropertyTransformer must not be null");
return add(new SimpleSecretBackendMetadata(path, propertyTransformer));
}
@Override
public SecretBackendConfigurer add(SecretBackendMetadata metadata) {
Assert.notNull(metadata, "SecretBackendMetadata must not be null");
secretBackends.put(metadata.getPath(), metadata);
return this;
}
@Override
public SecretBackendConfigurer registerDefaultGenericSecretBackends(
boolean registerDefault) {
this.registerDefaultGenericSecretBackends = registerDefault;
return this;
}
@Override
public SecretBackendConfigurer registerDefaultDiscoveredSecretBackends(
boolean registerDefault) {
this.registerDefaultDiscoveredSecretBackends = registerDefault;
return this;
}
public boolean isRegisterDefaultGenericSecretBackends() {
return registerDefaultGenericSecretBackends;
}
public boolean isRegisterDefaultDiscoveredSecretBackends() {
return registerDefaultDiscoveredSecretBackends;
}
@Override
public List<SecretBackendMetadata> getSecretBackends() {
return new ArrayList<>(secretBackends.values());
}
@RequiredArgsConstructor
private static class SimpleSecretBackendMetadata implements SecretBackendMetadata {
private final String path;
private final PropertyTransformer propertyTransformer;
@Override
public String getName() {
return String.format("Context backend: %s", path);
}
@Override
public String getPath() {
return path;
}
@Override
public PropertyTransformer getPropertyTransformer() {
return propertyTransformer;
}
@Override
public Map<String, String> getVariables() {
return Collections.singletonMap("path", path);
}
}
}

View File

@@ -16,36 +16,29 @@
package org.springframework.cloud.vault.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.vault.core.util.PropertyTransformer;
import org.springframework.vault.core.util.PropertyTransformers;
/**
* {@link SecretBackendMetadata} for the {@code generic} secret backend.
*
* @author Mark Paluch
*/
class GenericSecretBackendMetadata implements SecretBackendMetadata {
public class GenericSecretBackendMetadata extends SecretBackendMetadataSupport
implements SecretBackendMetadata {
private final String secretBackendPath;
private final String path;
private final String key;
private GenericSecretBackendMetadata(String path) {
private GenericSecretBackendMetadata(String secretBackendPath, String key) {
Assert.hasText(path, "Secret backend path must not be empty");
Assert.hasText(secretBackendPath, "Secret backend path must not be empty");
Assert.hasText(key, "Key must not be empty");
this.key = key;
this.secretBackendPath = secretBackendPath;
this.path = path;
}
/**
@@ -59,33 +52,30 @@ class GenericSecretBackendMetadata implements SecretBackendMetadata {
* @return the {@link SecretBackendMetadata}
*/
public static SecretBackendMetadata create(String secretBackendPath, String key) {
return new GenericSecretBackendMetadata(secretBackendPath, key);
Assert.hasText(secretBackendPath,
"Secret backend path must not be null or empty");
Assert.hasText(key, "Key must not be null or empty");
return create(String.format("%s/%s", secretBackendPath, key));
}
/**
* Create a {@link SecretBackendMetadata} for the {@code generic} secret backend given
* a {@code path}.
*
* @param path the relative path of the secret. slashes, must not be empty or
* {@literal null}.
* @return the {@link SecretBackendMetadata}
* @since 1.1
*/
public static SecretBackendMetadata create(String path) {
return new GenericSecretBackendMetadata(path);
}
@Override
public String getPath() {
return String.format("%s/%s", secretBackendPath, key);
}
@Override
public String getName() {
return getPath();
}
@Override
public PropertyTransformer getPropertyTransformer() {
return PropertyTransformers.noop();
}
@Override
public Map<String, String> getVariables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", secretBackendPath);
variables.put("key", key);
return variables;
return path;
}
/**
@@ -93,34 +83,51 @@ class GenericSecretBackendMetadata implements SecretBackendMetadata {
* Application name and profiles support multiple (comma-separated) values.
*
* @param genericBackendProperties
* @param environment
* @return
* @param profiles active application profiles.
* @return list of context paths.
*/
public static List<String> buildContexts(
VaultGenericBackendProperties genericBackendProperties,
Environment environment) {
List<String> profiles) {
String appName = genericBackendProperties.getApplicationName();
List<String> profiles = Arrays.asList(environment.getActiveProfiles());
List<String> contexts = new ArrayList<>();
Set<String> contexts = new LinkedHashSet<>();
String defaultContext = genericBackendProperties.getDefaultContext();
addContext(contexts, defaultContext, profiles, genericBackendProperties);
contexts.addAll(buildContexts(defaultContext, profiles,
genericBackendProperties.getProfileSeparator()));
for (String applicationName : StringUtils.commaDelimitedListToSet(appName)) {
addContext(contexts, applicationName, profiles, genericBackendProperties);
contexts.addAll(buildContexts(applicationName, profiles,
genericBackendProperties.getProfileSeparator()));
}
Collections.reverse(contexts);
return contexts;
List<String> result = new ArrayList<>(contexts);
Collections.reverse(result);
return result;
}
private static void addContext(List<String> contexts, String applicationName,
List<String> profiles,
VaultGenericBackendProperties genericBackendProperties) {
/**
* Create a list of context names from a combination of application name and
* application name with profile name. Using an empty application name will return an
* empty list.
*
* @param applicationName the application name. May be empty.
* @param profiles active application profiles.
* @param profileSeparator profile separator character between application name and
* profile name.
* @return list of context names.
* @since 1.1
*/
public static List<String> buildContexts(String applicationName,
List<String> profiles, String profileSeparator) {
List<String> contexts = new ArrayList<>();
if (!StringUtils.hasText(applicationName)) {
return;
return contexts;
}
if (!contexts.contains(applicationName)) {
@@ -133,12 +140,13 @@ class GenericSecretBackendMetadata implements SecretBackendMetadata {
continue;
}
String contextName = applicationName
+ genericBackendProperties.getProfileSeparator() + profile.trim();
String contextName = applicationName + profileSeparator + profile.trim();
if (!contexts.contains(contextName)) {
contexts.add(contextName);
}
}
return contexts;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.cloud.vault.config;
import java.net.URI;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.apachecommons.CommonsLog;
@@ -30,8 +28,6 @@ import org.springframework.vault.core.lease.SecretLeaseContainer;
import org.springframework.vault.core.lease.domain.RequestedSecret;
import org.springframework.vault.core.lease.event.LeaseErrorListener;
import org.springframework.vault.core.lease.event.SecretLeaseEvent;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
/**
* Extension to {@link LeasingVaultPropertySourceLocator} that creates
@@ -44,25 +40,23 @@ import org.springframework.web.util.UriTemplateHandler;
class LeasingVaultPropertySourceLocator extends VaultPropertySourceLocatorSupport
implements PriorityOrdered {
private static final UriTemplateHandler TEMPLATE_HANDLER = new DefaultUriTemplateHandler();
private final SecretLeaseContainer secretLeaseContainer;
private final VaultProperties properties;
/**
* Creates a new {@link LeasingVaultPropertySourceLocator}.
*
* @param properties must not be {@literal null}.
* @param genericBackendProperties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
* @param propertySourceLocatorConfiguration must not be {@literal null}.
* @param secretLeaseContainer must not be {@literal null}.
* @since 1.1
*/
public LeasingVaultPropertySourceLocator(VaultProperties properties,
VaultGenericBackendProperties genericBackendProperties,
Collection<SecretBackendMetadata> backendAccessors,
PropertySourceLocatorConfiguration propertySourceLocatorConfiguration,
SecretLeaseContainer secretLeaseContainer) {
super("vault", genericBackendProperties, backendAccessors);
super("vault", propertySourceLocatorConfiguration);
Assert.notNull(secretLeaseContainer, "SecretLeaseContainer must not be null");
Assert.notNull(properties, "VaultProperties must not be null");
@@ -86,9 +80,7 @@ class LeasingVaultPropertySourceLocator extends VaultPropertySourceLocatorSuppor
protected PropertySource<?> createVaultPropertySource(
SecretBackendMetadata accessor) {
URI expand = TEMPLATE_HANDLER.expand("{backend}/{key}", accessor.getVariables());
final RequestedSecret secret = RequestedSecret.renewable(expand.getPath());
RequestedSecret secret = RequestedSecret.renewable(accessor.getPath());
if (properties.isFailFast()) {
return createVaultPropertySourceFailFast(secret, accessor);

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2017 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;
/**
* Configuration for a Vault
* {@link org.springframework.cloud.bootstrap.config.PropertySourceLocator}.
*
* @author Mark Paluch
* @since 1.1
*/
public interface PropertySourceLocatorConfiguration {
/**
* Return a {@link Collection} of {@link SecretBackendMetadata} to be instantiated as
* {@link org.springframework.core.env.PropertySource}.
*
* @return a {@link Collection} of {@link SecretBackendMetadata}.
*/
Collection<SecretBackendMetadata> getSecretBackends();
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2017 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.vault.core.util.PropertyTransformer;
/**
* Helps to configure {@link SecretBackendMetadata secret backends} with support for
* {@link PropertyTransformer property transformers}.
*
* <p>
* Assists configuration with a fluent style. This configurer allows configuration via
* context paths and direct registration of {@link SecretBackendMetadata}.
* <p>
* Use {@link #registerDefaultGenericSecretBackends(boolean)} to register default generic
* secret backend property sources and
* {@link #registerDefaultDiscoveredSecretBackends(boolean)} to register additional secret
* backend property sources such as MySQL and RabbitMQ.
*
* @author Mark Paluch
* @since 1.1
* @see PropertyTransformer
* @see SecretBackendMetadata
*/
public interface SecretBackendConfigurer {
/**
* Add a {@link SecretBackendMetadata} given its {@code path}.
*
* @param path must not be {@literal null} or empty.
* @return {@code this} {@link SecretBackendConfigurer}.
*/
SecretBackendConfigurer add(String path);
/**
* Add a {@link SecretBackendMetadata} given its {@code path} and
* {@link PropertyTransformer}.
*
* @param path must not be {@literal null} or empty.
* @param propertyTransformer must not be {@literal null}.
* @return {@code this} {@link SecretBackendConfigurer}.
*/
SecretBackendConfigurer add(String path, PropertyTransformer propertyTransformer);
/**
* Add a {@link SecretBackendMetadata}.
*
* @param metadata must not be {@literal null}.
* @return {@code this} {@link SecretBackendConfigurer}.
*/
SecretBackendConfigurer add(SecretBackendMetadata metadata);
/**
* Register default generic secret backend property sources.
*
* @param registerDefault {@literal true} to enable default generic secret backend
* registration.
* @return {@code this} {@link SecretBackendConfigurer}.
*/
SecretBackendConfigurer registerDefaultGenericSecretBackends(boolean registerDefault);
/**
* Register default discovered secret backend property sources from
* {@link SecretBackendMetadata} via {@link VaultSecretBackendDescriptor} beans.
*
* @param registerDefault {@literal true} to enable default discovered secret backend
* registration via {@link VaultSecretBackendDescriptor} beans.
* @return {@code this} {@link SecretBackendConfigurer}.
*/
SecretBackendConfigurer registerDefaultDiscoveredSecretBackends(
boolean registerDefault);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2017 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.Collections;
import java.util.Map;
import org.springframework.vault.core.util.PropertyTransformer;
import org.springframework.vault.core.util.PropertyTransformers;
/**
* Support class for {@link SecretBackendMetadata} implementations. Implementing classes
* are required to implement {@link #getPath()} to derive name and variables from the
* path.
*
* @author Mark Paluch
* @since 1.1
*/
public abstract class SecretBackendMetadataSupport implements SecretBackendMetadata {
@Override
public String getName() {
return getPath();
}
@Override
public PropertyTransformer getPropertyTransformer() {
return PropertyTransformers.noop();
}
@Override
public Map<String, String> getVariables() {
return Collections.singletonMap("path", getPath());
}
}

View File

@@ -16,7 +16,9 @@
package org.springframework.cloud.vault.config;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
@@ -31,6 +33,8 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.scheduling.TaskScheduler;
@@ -69,6 +73,8 @@ import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestOperations;
import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.create;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Vault support.
*
@@ -79,6 +85,7 @@ import org.springframework.web.client.RestOperations;
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
@EnableConfigurationProperties({ VaultProperties.class,
VaultGenericBackendProperties.class })
@Order(Ordered.LOWEST_PRECEDENCE - 10)
public class VaultBootstrapConfiguration implements InitializingBean {
private final ConfigurableApplicationContext applicationContext;
@@ -134,11 +141,12 @@ public class VaultBootstrapConfiguration implements InitializingBean {
VaultGenericBackendProperties vaultGenericBackendProperties,
ObjectFactory<SecretLeaseContainer> secretLeaseContainerObjectFactory) {
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
.createSecretBackendMetadata(vaultSecretBackendDescriptors, factories);
VaultConfigTemplate vaultConfigTemplate = new VaultConfigTemplate(operations,
vaultProperties);
PropertySourceLocatorConfiguration propertySourceLocatorConfiguration = getPropertySourceConfiguration(
vaultGenericBackendProperties);
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
// This is to destroy bootstrap resources
@@ -150,12 +158,65 @@ public class VaultBootstrapConfiguration implements InitializingBean {
secretLeaseContainer.start();
return new LeasingVaultPropertySourceLocator(vaultProperties,
vaultGenericBackendProperties, backendAccessors,
secretLeaseContainer);
propertySourceLocatorConfiguration, secretLeaseContainer);
}
return new VaultPropertySourceLocator(vaultConfigTemplate, vaultProperties,
vaultGenericBackendProperties, backendAccessors);
propertySourceLocatorConfiguration);
}
private PropertySourceLocatorConfiguration getPropertySourceConfiguration(
VaultGenericBackendProperties vaultGenericBackendProperties) {
Collection<VaultConfigurer> configurers = applicationContext
.getBeansOfType(VaultConfigurer.class).values();
DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer();
if (configurers.isEmpty()) {
secretBackendConfigurer.registerDefaultGenericSecretBackends(true)
.registerDefaultDiscoveredSecretBackends(true);
}
else {
for (VaultConfigurer vaultConfigurer : configurers) {
vaultConfigurer.addSecretBackends(secretBackendConfigurer);
}
}
if (secretBackendConfigurer.isRegisterDefaultGenericSecretBackends()) {
if (vaultGenericBackendProperties.isEnabled()) {
List<String> contexts = GenericSecretBackendMetadata
.buildContexts(vaultGenericBackendProperties, Arrays.asList(
applicationContext.getEnvironment().getActiveProfiles()));
for (String context : contexts) {
secretBackendConfigurer.add(
create(vaultGenericBackendProperties.getBackend(), context));
}
}
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
.createSecretBackendMetadata(vaultSecretBackendDescriptors,
factories);
for (SecretBackendMetadata metadata : backendAccessors) {
secretBackendConfigurer.add(metadata);
}
}
if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) {
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
.createSecretBackendMetadata(vaultSecretBackendDescriptors,
factories);
for (SecretBackendMetadata metadata : backendAccessors) {
secretBackendConfigurer.add(metadata);
}
}
return secretBackendConfigurer;
}
/**

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2017 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;
/**
* Defines callback methods to customize the configuration for Spring Cloud Vault
* applications.
*
* <p>
* Configuration classes may implement this interface to be called back and given a chance
* to customize the default configuration. Consider implementing this interface and
* overriding the relevant methods for your needs.
*
* <p>
* Registered bean instances of {@link VaultConfigurer} disable default secret backend
* registration for the generic and integrative (other discovered
* {@link SecretBackendMetadata}) backends. See
* {@link SecretBackendConfigurer#registerDefaultGenericSecretBackends(boolean)} and
* {@link SecretBackendConfigurer#registerDefaultDiscoveredSecretBackends(boolean)} for
* more details.
*
* @author Mark Paluch
* @since 1.1
* @see SecretBackendConfigurer
*/
public interface VaultConfigurer {
/**
* Configure the secret backends that are instantiated as
* {@link org.springframework.core.env.PropertySource property sources}.
*
* @param configurer the {@link SecretBackendConfigurer} to configure secret backends,
* must not be {@literal null}.
*/
void addSecretBackends(SecretBackendConfigurer configurer);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.cloud.vault.config;
import java.util.Collection;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
@@ -42,15 +40,14 @@ class VaultPropertySourceLocator extends VaultPropertySourceLocatorSupport
*
* @param operations must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param genericBackendProperties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
* @param propertySourceLocatorConfiguration must not be {@literal null}.
* @since 1.1
*/
public VaultPropertySourceLocator(VaultConfigOperations operations,
VaultProperties properties,
VaultGenericBackendProperties genericBackendProperties,
Collection<SecretBackendMetadata> backendAccessors) {
PropertySourceLocatorConfiguration propertySourceLocatorConfiguration) {
super("vault", genericBackendProperties, backendAccessors);
super("vault", propertySourceLocatorConfiguration);
Assert.notNull(operations, "VaultConfigOperations must not be null");
Assert.notNull(properties, "VaultProperties must not be null");

View File

@@ -16,15 +16,20 @@
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 lombok.RequiredArgsConstructor;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.create;
@@ -38,9 +43,7 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
private final String propertySourceName;
private final VaultGenericBackendProperties genericBackendProperties;
private final Collection<SecretBackendMetadata> backendAccessors;
private final PropertySourceLocatorConfiguration propertySourceLocatorConfiguration;
/**
* Creates a new {@link VaultPropertySourceLocatorSupport}.
@@ -53,19 +56,63 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
VaultGenericBackendProperties genericBackendProperties,
Collection<SecretBackendMetadata> backendAccessors) {
this(propertySourceName,
createConfiguration(genericBackendProperties, backendAccessors));
}
/**
* Creates a new {@link VaultPropertySourceLocatorSupport} given a
* {@link PropertySourceLocatorConfiguration}.
*
* @param propertySourceName must not be {@literal null} or empty.
* @param propertySourceLocatorConfiguration must not be {@literal null}.
* @since 1.1
*/
public VaultPropertySourceLocatorSupport(String propertySourceName,
PropertySourceLocatorConfiguration propertySourceLocatorConfiguration) {
Assert.hasText(propertySourceName, "PropertySource name must not be empty");
Assert.notNull(propertySourceLocatorConfiguration,
"PropertySourceLocatorConfiguration must not be null");
this.propertySourceName = propertySourceName;
this.propertySourceLocatorConfiguration = propertySourceLocatorConfiguration;
}
static PropertySourceLocatorConfiguration createConfiguration(
VaultGenericBackendProperties genericBackendProperties,
Collection<SecretBackendMetadata> backendAccessors) {
Assert.notNull(genericBackendProperties,
"VaultGenericBackendProperties must not be null");
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
GenericPropertySourceLocatorConfiguration generic = new GenericPropertySourceLocatorConfiguration(
genericBackendProperties);
WrappedPropertySourceLocatorConfiguration backends = new WrappedPropertySourceLocatorConfiguration(
new ArrayList<>(backendAccessors));
return new CompositePropertySourceConfiguration(generic, backends);
}
static PropertySourceLocatorConfiguration createConfiguration(
VaultGenericBackendProperties genericBackendProperties) {
Assert.notNull(genericBackendProperties,
"VaultGenericBackendProperties must not be null");
this.propertySourceName = propertySourceName;
this.backendAccessors = backendAccessors;
this.genericBackendProperties = genericBackendProperties;
return new GenericPropertySourceLocatorConfiguration(genericBackendProperties);
}
@Override
public PropertySource<?> locate(Environment environment) {
if (propertySourceLocatorConfiguration instanceof EnvironmentAware) {
((EnvironmentAware) propertySourceLocatorConfiguration)
.setEnvironment(environment);
}
CompositePropertySource propertySource = createCompositePropertySource(
environment);
initialize(propertySource);
@@ -109,13 +156,16 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
*/
protected List<PropertySource<?>> doCreatePropertySources(Environment environment) {
Collection<SecretBackendMetadata> secretBackends = propertySourceLocatorConfiguration
.getSecretBackends();
List<SecretBackendMetadata> sorted = new ArrayList<>(secretBackends);
List<PropertySource<?>> propertySources = new ArrayList<>();
if (genericBackendProperties.isEnabled()) {
propertySources.addAll(doCreateGenericPropertySources(environment));
}
AnnotationAwareOrderComparator.sort(sorted);
for (SecretBackendMetadata backendAccessor : backendAccessors) {
propertySources.addAll(doCreateGenericPropertySources(environment));
for (SecretBackendMetadata backendAccessor : sorted) {
PropertySource<?> vaultPropertySource = createVaultPropertySource(
backendAccessor);
@@ -135,23 +185,7 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
*/
protected List<PropertySource<?>> doCreateGenericPropertySources(
Environment environment) {
List<PropertySource<?>> propertySources = new ArrayList<>();
List<String> contexts = GenericSecretBackendMetadata
.buildContexts(genericBackendProperties, environment);
for (String propertySourceContext : contexts) {
if (StringUtils.hasText(propertySourceContext)) {
PropertySource<?> vaultPropertySource = createVaultPropertySource(create(
genericBackendProperties.getBackend(), propertySourceContext));
propertySources.add(vaultPropertySource);
}
}
return propertySources;
return new ArrayList<>();
}
/**
@@ -185,4 +219,89 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
protected abstract PropertySource<?> createVaultPropertySource(
SecretBackendMetadata accessor);
@RequiredArgsConstructor
private static class GenericPropertySourceLocatorConfiguration
implements EnvironmentAware, PropertySourceLocatorConfiguration {
private final VaultGenericBackendProperties genericBackendProperties;
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public Collection<SecretBackendMetadata> getSecretBackends() {
if (genericBackendProperties.isEnabled()) {
List<String> contexts = GenericSecretBackendMetadata.buildContexts(
genericBackendProperties,
Arrays.asList(environment.getActiveProfiles()));
List<SecretBackendMetadata> result = new ArrayList<>(contexts.size());
for (String context : contexts) {
result.add(create(genericBackendProperties.getBackend(), context));
}
return result;
}
return Collections.emptyList();
}
}
@RequiredArgsConstructor
private static class WrappedPropertySourceLocatorConfiguration
implements PropertySourceLocatorConfiguration {
private final List<SecretBackendMetadata> metadata;
@Override
public Collection<SecretBackendMetadata> getSecretBackends() {
return metadata;
}
}
private static class CompositePropertySourceConfiguration
implements PropertySourceLocatorConfiguration, EnvironmentAware {
private final List<PropertySourceLocatorConfiguration> configurations;
public CompositePropertySourceConfiguration(
PropertySourceLocatorConfiguration... configurations) {
List<PropertySourceLocatorConfiguration> copy = new ArrayList<>(
Arrays.asList(configurations));
AnnotationAwareOrderComparator.sortIfNecessary(copy);
this.configurations = copy;
}
@Override
public Collection<SecretBackendMetadata> getSecretBackends() {
List<SecretBackendMetadata> result = new ArrayList<>();
for (PropertySourceLocatorConfiguration configuration : configurations) {
result.addAll(configuration.getSecretBackends());
}
return result;
}
@Override
public void setEnvironment(Environment environment) {
for (PropertySourceLocatorConfiguration configuration : configurations) {
if (configuration instanceof EnvironmentAware) {
((EnvironmentAware) configuration).setEnvironment(environment);
}
}
}
}
}

View File

@@ -15,12 +15,12 @@
*/
package org.springframework.cloud.vault.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -30,14 +30,13 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class GenericSecretBackendMetadataUnitTests {
MockEnvironment environment = new MockEnvironment();
VaultGenericBackendProperties properties = new VaultGenericBackendProperties();
@Test
public void shouldCreateDefaultContexts() {
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Collections.<String>emptyList());
assertThat(contexts).hasSize(1).contains("application");
}
@@ -48,7 +47,7 @@ public class GenericSecretBackendMetadataUnitTests {
properties.setApplicationName("my-app");
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Collections.<String>emptyList());
assertThat(contexts).hasSize(2).containsSequence("my-app", "application");
}
@@ -57,11 +56,9 @@ public class GenericSecretBackendMetadataUnitTests {
public void shouldCreateDefaultForAppNameAndDefaultContextWithProfiles() {
properties.setApplicationName("my-app");
environment.addActiveProfile("cloud");
environment.addActiveProfile("local");
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Arrays.asList("cloud", "local"));
assertThat(contexts).hasSize(6).containsSequence("my-app/local", "my-app/cloud",
"my-app", "application/local", "application/cloud", "application");
@@ -74,7 +71,7 @@ public class GenericSecretBackendMetadataUnitTests {
properties.setDefaultContext("");
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Collections.<String>emptyList());
assertThat(contexts).hasSize(1).containsSequence("my-app");
}
@@ -85,7 +82,7 @@ public class GenericSecretBackendMetadataUnitTests {
properties.setApplicationName("foo,bar");
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Collections.<String>emptyList());
assertThat(contexts).hasSize(3).containsSequence("bar", "foo", "application");
}
@@ -93,13 +90,10 @@ public class GenericSecretBackendMetadataUnitTests {
@Test
public void shouldCreateContextsWithProfile() {
environment.addActiveProfile("cloud");
environment.addActiveProfile("local");
properties.setApplicationName("foo,bar");
List<String> contexts = GenericSecretBackendMetadata.buildContexts(properties,
environment);
Arrays.asList("cloud", "local"));
assertThat(contexts).hasSize(9).containsSequence("bar/local", "bar/cloud", "bar",
"foo/local", "foo/cloud", "foo", "application/local", "application/cloud",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.cloud.vault.config;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,8 +49,9 @@ public class LeasingVaultPropertySourceLocatorUnitTests {
public void before() {
propertySourceLocator = new LeasingVaultPropertySourceLocator(
new VaultProperties(), new VaultGenericBackendProperties(),
Collections.<SecretBackendMetadata>emptyList(), secretLeaseContainer);
new VaultProperties(), VaultPropertySourceLocatorSupport
.createConfiguration(new VaultGenericBackendProperties()),
secretLeaseContainer);
}
@Test
@@ -62,8 +61,9 @@ public class LeasingVaultPropertySourceLocatorUnitTests {
vaultProperties.getConfig().setOrder(10);
propertySourceLocator = new LeasingVaultPropertySourceLocator(vaultProperties,
new VaultGenericBackendProperties(),
Collections.<SecretBackendMetadata>emptyList(), secretLeaseContainer);
VaultPropertySourceLocatorSupport.createConfiguration(
new VaultGenericBackendProperties()),
secretLeaseContainer);
assertThat(propertySourceLocator.getOrder()).isEqualTo(10);
}

View File

@@ -50,7 +50,8 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { BootstrapConfiguration.class,
VaultConfigAppIdCustomMechanismTests.TestApplication.class }, properties = {
"spring.cloud.vault.authentication=appid", "use.custom.config=true",
"spring.cloud.vault.authentication=appid",
"VaultConfigAppIdCustomMechanismTests.custom.config=true",
"spring.cloud.vault.applicationName=VaultConfigAppIdCustomMechanismTests" })
public class VaultConfigAppIdCustomMechanismTests {
@@ -119,7 +120,7 @@ public class VaultConfigAppIdCustomMechanismTests {
@Configuration
public static class BootstrapConfiguration {
@ConditionalOnProperty("use.custom.config")
@ConditionalOnProperty("VaultConfigAppIdCustomMechanismTests.custom.config")
@Bean
ClientAuthentication clientAuthentication() {

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2017 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.Collections;
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.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.vault.core.VaultOperations;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test using config infrastructure with token authentication.
* <p>
* In case this test should fail because of SSL make sure you run the test within the
* spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is
* referenced with {@code ../work/keystore.jks}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = VaultConfigWithVaultConfigurerTests.TestApplication.class, properties = "VaultConfigWithVaultConfigurerTests.custom.config=true")
public class VaultConfigWithVaultConfigurerTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
VaultOperations vaultOperations = vaultRule.prepare().getVaultOperations();
vaultOperations.write("secret/VaultConfigWithVaultConfigurerTests",
Collections.singletonMap("vault.value", "hello"));
vaultOperations.write("secret/testVaultApp",
Collections.singletonMap("vault.value", "world"));
}
@Value("${vault.value}")
String configValue;
@Test
public void contextLoads() {
assertThat(configValue).isEqualTo("hello");
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
public static class ConfigurerBootstrapApplication {
@ConditionalOnProperty("VaultConfigWithVaultConfigurerTests.custom.config")
@Bean
VaultConfigurer vaultConfigurer() {
return new VaultConfigurer() {
@Override
public void addSecretBackends(SecretBackendConfigurer configurer) {
configurer.add("secret/VaultConfigWithVaultConfigurerTests");
}
};
}
}
}

View File

@@ -15,14 +15,13 @@
*/
package org.springframework.cloud.vault.config;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
@@ -53,8 +52,8 @@ public class VaultPropertySourceLocatorUnitTests {
@Before
public void before() {
propertySourceLocator = new VaultPropertySourceLocator(operations,
new VaultProperties(), new VaultGenericBackendProperties(),
Collections.<SecretBackendMetadata>emptyList());
new VaultProperties(), VaultPropertySourceLocatorSupport
.createConfiguration(new VaultGenericBackendProperties()));
}
@Test
@@ -64,8 +63,8 @@ public class VaultPropertySourceLocatorUnitTests {
vaultProperties.getConfig().setOrder(42);
propertySourceLocator = new VaultPropertySourceLocator(operations,
vaultProperties, new VaultGenericBackendProperties(),
Collections.<SecretBackendMetadata>emptyList());
vaultProperties, VaultPropertySourceLocatorSupport
.createConfiguration(new VaultGenericBackendProperties()));
assertThat(propertySourceLocator.getOrder()).isEqualTo(42);
}
@@ -102,11 +101,13 @@ public class VaultPropertySourceLocatorUnitTests {
@Test
public void shouldLocatePropertySourcesInVaultApplicationContext() {
final VaultGenericBackendProperties backendProperties = new VaultGenericBackendProperties();
VaultGenericBackendProperties backendProperties = new VaultGenericBackendProperties();
backendProperties.setApplicationName("wintermute");
propertySourceLocator = new VaultPropertySourceLocator(operations,
new VaultProperties(), backendProperties,
Collections.<SecretBackendMetadata>emptyList());
new VaultProperties(),
VaultPropertySourceLocatorSupport.createConfiguration(backendProperties));
when(configurableEnvironment.getActiveProfiles())
.thenReturn(new String[] { "vermillion", "periwinkle" });
@@ -124,11 +125,13 @@ public class VaultPropertySourceLocatorUnitTests {
@Test
public void shouldLocatePropertySourcesInEachPathSpecifiedWhenApplicationNameContainsSeveral() {
final VaultGenericBackendProperties backendProperties = new VaultGenericBackendProperties();
VaultGenericBackendProperties backendProperties = new VaultGenericBackendProperties();
backendProperties.setApplicationName("wintermute,straylight,icebreaker/armitage");
propertySourceLocator = new VaultPropertySourceLocator(operations,
new VaultProperties(), backendProperties,
Collections.<SecretBackendMetadata>emptyList());
new VaultProperties(),
VaultPropertySourceLocatorSupport.createConfiguration(backendProperties));
when(configurableEnvironment.getActiveProfiles())
.thenReturn(new String[] { "vermillion", "periwinkle" });
@@ -146,4 +149,42 @@ public class VaultPropertySourceLocatorUnitTests {
"secret/icebreaker/armitage/vermillion",
"secret/icebreaker/armitage/periwinkle");
}
@Test
public void shouldCreatePropertySourcesInOrder() {
DefaultSecretBackendConfigurer configurer = new DefaultSecretBackendConfigurer();
configurer.add(new MySecondSecretBackendMetadata());
configurer.add(new MyFirstSecretBackendMetadata());
propertySourceLocator = new VaultPropertySourceLocator(operations,
new VaultProperties(), configurer);
PropertySource<?> propertySource = propertySourceLocator
.locate(configurableEnvironment);
assertThat(propertySource).isInstanceOf(CompositePropertySource.class);
CompositePropertySource composite = (CompositePropertySource) propertySource;
assertThat(composite.getPropertySources()).extracting("name")
.containsSequence("foo", "bar");
}
@Order(1)
static class MyFirstSecretBackendMetadata extends SecretBackendMetadataSupport {
@Override
public String getPath() {
return "foo";
}
}
@Order(2)
static class MySecondSecretBackendMetadata extends SecretBackendMetadataSupport {
@Override
public String getPath() {
return "bar";
}
}
}

View File

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