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