From 2fe14c2a7b1f342e7f6b98e6bd96e9d302f2ec60 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Sun, 21 May 2017 17:25:14 +0200 Subject: [PATCH] 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. --- .../src/main/asciidoc/spring-cloud-vault.adoc | 39 ++++ .../DefaultSecretBackendConfigurer.java | 131 +++++++++++++ .../config/GenericSecretBackendMetadata.java | 114 ++++++------ .../LeasingVaultPropertySourceLocator.java | 22 +-- .../PropertySourceLocatorConfiguration.java | 36 ++++ .../vault/config/SecretBackendConfigurer.java | 85 +++++++++ .../config/SecretBackendMetadataSupport.java | 48 +++++ .../config/VaultBootstrapConfiguration.java | 71 ++++++- .../cloud/vault/config/VaultConfigurer.java | 49 +++++ .../config/VaultPropertySourceLocator.java | 13 +- .../VaultPropertySourceLocatorSupport.java | 175 +++++++++++++++--- ...GenericSecretBackendMetadataUnitTests.java | 22 +-- ...ngVaultPropertySourceLocatorUnitTests.java | 14 +- .../VaultConfigAppIdCustomMechanismTests.java | 5 +- .../VaultConfigWithVaultConfigurerTests.java | 94 ++++++++++ .../VaultPropertySourceLocatorUnitTests.java | 65 +++++-- .../test/resources/META-INF/spring.factories | 3 +- 17 files changed, 841 insertions(+), 145 deletions(-) create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/PropertySourceLocatorConfiguration.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataSupport.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigurer.java create mode 100644 spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigWithVaultConfigurerTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-vault.adoc b/docs/src/main/asciidoc/spring-cloud-vault.adoc index ec1168b1..adc2f519 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault.adoc @@ -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 diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java new file mode 100644 index 00000000..9d3a1cbe --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java @@ -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 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 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 getVariables() { + return Collections.singletonMap("path", path); + } + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadata.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadata.java index a1ee1a31..8b4997a7 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadata.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadata.java @@ -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 getVariables() { - - Map 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 buildContexts( VaultGenericBackendProperties genericBackendProperties, - Environment environment) { + List profiles) { String appName = genericBackendProperties.getApplicationName(); - List profiles = Arrays.asList(environment.getActiveProfiles()); - List contexts = new ArrayList<>(); + Set 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 result = new ArrayList<>(contexts); + + Collections.reverse(result); + + return result; } - private static void addContext(List contexts, String applicationName, - List 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 buildContexts(String applicationName, + List profiles, String profileSeparator) { + + List 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; } } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java index cbe0492f..37589b5d 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java @@ -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 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); diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/PropertySourceLocatorConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/PropertySourceLocatorConfiguration.java new file mode 100644 index 00000000..a3112dc2 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/PropertySourceLocatorConfiguration.java @@ -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 getSecretBackends(); +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java new file mode 100644 index 00000000..53367274 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java @@ -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}. + * + *

+ * Assists configuration with a fluent style. This configurer allows configuration via + * context paths and direct registration of {@link SecretBackendMetadata}. + *

+ * 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); +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataSupport.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataSupport.java new file mode 100644 index 00000000..2bef87db --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataSupport.java @@ -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 getVariables() { + return Collections.singletonMap("path", getPath()); + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java index b609da20..1c0f274c 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java @@ -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 secretLeaseContainerObjectFactory) { - Collection 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 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 contexts = GenericSecretBackendMetadata + .buildContexts(vaultGenericBackendProperties, Arrays.asList( + applicationContext.getEnvironment().getActiveProfiles())); + + for (String context : contexts) { + secretBackendConfigurer.add( + create(vaultGenericBackendProperties.getBackend(), context)); + } + } + + Collection backendAccessors = SecretBackendFactories + .createSecretBackendMetadata(vaultSecretBackendDescriptors, + factories); + for (SecretBackendMetadata metadata : backendAccessors) { + secretBackendConfigurer.add(metadata); + } + } + + if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) { + + Collection backendAccessors = SecretBackendFactories + .createSecretBackendMetadata(vaultSecretBackendDescriptors, + factories); + for (SecretBackendMetadata metadata : backendAccessors) { + secretBackendConfigurer.add(metadata); + } + } + + return secretBackendConfigurer; } /** diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigurer.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigurer.java new file mode 100644 index 00000000..3dc0e5ea --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigurer.java @@ -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. + * + *

+ * 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. + * + *

+ * 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); +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java index d2d6caff..02b1502d 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocator.java @@ -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 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"); diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorSupport.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorSupport.java index 00a9e4a9..b2d9eef1 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorSupport.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorSupport.java @@ -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 backendAccessors; + private final PropertySourceLocatorConfiguration propertySourceLocatorConfiguration; /** * Creates a new {@link VaultPropertySourceLocatorSupport}. @@ -53,19 +56,63 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc VaultGenericBackendProperties genericBackendProperties, Collection 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 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> doCreatePropertySources(Environment environment) { + Collection secretBackends = propertySourceLocatorConfiguration + .getSecretBackends(); + List sorted = new ArrayList<>(secretBackends); List> 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> doCreateGenericPropertySources( Environment environment) { - - List> propertySources = new ArrayList<>(); - List 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 getSecretBackends() { + + if (genericBackendProperties.isEnabled()) { + + List contexts = GenericSecretBackendMetadata.buildContexts( + genericBackendProperties, + Arrays.asList(environment.getActiveProfiles())); + + List 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 metadata; + + @Override + public Collection getSecretBackends() { + return metadata; + } + } + + private static class CompositePropertySourceConfiguration + implements PropertySourceLocatorConfiguration, EnvironmentAware { + + private final List configurations; + + public CompositePropertySourceConfiguration( + PropertySourceLocatorConfiguration... configurations) { + + List copy = new ArrayList<>( + Arrays.asList(configurations)); + + AnnotationAwareOrderComparator.sortIfNecessary(copy); + + this.configurations = copy; + } + + @Override + public Collection getSecretBackends() { + + List 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); + } + } + } + } } diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadataUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadataUnitTests.java index c28d976e..9ea8baed 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadataUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/GenericSecretBackendMetadataUnitTests.java @@ -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 contexts = GenericSecretBackendMetadata.buildContexts(properties, - environment); + Collections.emptyList()); assertThat(contexts).hasSize(1).contains("application"); } @@ -48,7 +47,7 @@ public class GenericSecretBackendMetadataUnitTests { properties.setApplicationName("my-app"); List contexts = GenericSecretBackendMetadata.buildContexts(properties, - environment); + Collections.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 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 contexts = GenericSecretBackendMetadata.buildContexts(properties, - environment); + Collections.emptyList()); assertThat(contexts).hasSize(1).containsSequence("my-app"); } @@ -85,7 +82,7 @@ public class GenericSecretBackendMetadataUnitTests { properties.setApplicationName("foo,bar"); List contexts = GenericSecretBackendMetadata.buildContexts(properties, - environment); + Collections.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 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", diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java index 4daead1c..ee51204c 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java @@ -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.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.emptyList(), secretLeaseContainer); + VaultPropertySourceLocatorSupport.createConfiguration( + new VaultGenericBackendProperties()), + secretLeaseContainer); assertThat(propertySourceLocator.getOrder()).isEqualTo(10); } diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigAppIdCustomMechanismTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigAppIdCustomMechanismTests.java index fbdc3216..26ed5172 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigAppIdCustomMechanismTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigAppIdCustomMechanismTests.java @@ -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() { diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigWithVaultConfigurerTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigWithVaultConfigurerTests.java new file mode 100644 index 00000000..069266b0 --- /dev/null +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigWithVaultConfigurerTests.java @@ -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. + *

+ * 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"); + } + }; + } + } +} diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorUnitTests.java index cf78ad22..4e58b578 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorUnitTests.java @@ -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.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.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.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.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"; + } + } } diff --git a/spring-cloud-vault-config/src/test/resources/META-INF/spring.factories b/spring-cloud-vault-config/src/test/resources/META-INF/spring.factories index cc4abbfb..a34e0316 100644 --- a/spring-cloud-vault-config/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-vault-config/src/test/resources/META-INF/spring.factories @@ -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