diff --git a/docs/src/main/asciidoc/config-data.adoc b/docs/src/main/asciidoc/config-data.adoc index 720b3134a..ba8f6bbed 100644 --- a/docs/src/main/asciidoc/config-data.adoc +++ b/docs/src/main/asciidoc/config-data.adoc @@ -38,6 +38,22 @@ spring.config.import: vault://first/context/path, vault://other/path, vault:// ---- ==== +Property names within a Spring `Environment` must be unique to avoid shadowing. +If you use the same secret names in different context paths and you want to expose these as individual properties you can distinguish them by adding a `prefix` query parameter to the location. + +.application.yml +==== +[source,yaml] +---- +spring.config.import: vault://my/path?prefix=foo., vault://my/other/path?prefix=bar. +secret: ${foo.secret} +other.secret: ${bar.secret} +---- + +NOTE: Prefixes are added as-is to all property names returned by Vault. If you want key names to be separated with a dot between the prefix and key name, make sure to add a trailing dot to the prefix. + +==== + [[vault.configdata.location.optional]] === Conditionally enable/disable Vault Configuration @@ -50,18 +66,6 @@ Optional locations are skipped during application startup if Vault support was d NOTE: Vault context paths that cannot be found (HTTP Status 404) are skipped regardless of whether the config location is marked optional. <> allows failing on start if a Vault context path cannot be found because of HTTP Status 404. -If you have the same secret names in different paths, you can distinguish them by using a prefix on the path. - -.application.yml -==== -[source,yaml] ----- -spring.config.import: vault://my/prefixed/path?prefix=prefix1, vault://my/other/path?prefix=prefix2 -secret: ${prefix1.secret} -other.secret: ${prefix2.secret} ----- - -==== [[vault.configdata.customization]] === Infrastructure Customization diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java index e86c2ce72..9fdaec470 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolver.java @@ -77,6 +77,7 @@ * required later on by {@link VaultConfigDataLoader}. * * @author Mark Paluch + * @author Jeffrey van der Laan * @since 3.0 * @see VaultConfigurer * @see BootstrapRegistry @@ -118,17 +119,20 @@ public List resolveProfileSpecific(ConfigDataLocationResolv contextPath = contextPath.substring(1); } + return Collections.singletonList( + new VaultConfigLocation(contextPath, getPropertyTransformer(contextPath), location.isOptional())); + } + + private static PropertyTransformer getPropertyTransformer(String contextPath) { + UriComponents uriComponents = UriComponentsBuilder.fromUriString(contextPath).build(); String prefix = uriComponents.getQueryParams().getFirst("prefix"); - String path = uriComponents.getPath(); - if (StringUtils.hasLength(prefix) && StringUtils.hasLength(path)) { - PropertyTransformer keyPrefixPropertyTransformer = PropertyTransformers.propertyNamePrefix(prefix); - SecretBackendMetadata secretBackendMetadata = KeyValueSecretBackendMetadata.create(path, keyPrefixPropertyTransformer); - return Collections.singletonList(new VaultConfigLocation(secretBackendMetadata, location.isOptional())); - } - else { - return Collections.singletonList(new VaultConfigLocation(contextPath, location.isOptional())); + + if (StringUtils.hasText(prefix) && StringUtils.hasText(uriComponents.getPath())) { + return PropertyTransformers.propertyNamePrefix(prefix); } + + return PropertyTransformers.noop(); } private static void registerVaultProperties(ConfigDataLocationResolverContext context) { diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigLocation.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigLocation.java index 522e64351..7a893dfd6 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigLocation.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultConfigLocation.java @@ -20,6 +20,8 @@ import org.springframework.boot.context.config.ConfigDataResource; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import org.springframework.vault.core.util.PropertyTransformer; +import org.springframework.vault.core.util.PropertyTransformers; /** * Vault-specific implementation for a {@link ConfigDataLocation}. Consists of a @@ -40,17 +42,38 @@ public class VaultConfigLocation extends ConfigDataResource { private final boolean optional; + /** + * Create a new {@link VaultConfigLocation} instance. + * @param contextPath the context path + * @param optional if the resource is optional + */ public VaultConfigLocation(String contextPath, boolean optional) { + this(contextPath, PropertyTransformers.noop(), optional); + } + + /** + * Create a new {@link VaultConfigLocation} instance. + * @param contextPath the context path + * @param propertyTransformer the property transformer + * @param optional if the resource is optional + * @since 3.0.4 + */ + public VaultConfigLocation(String contextPath, PropertyTransformer propertyTransformer, boolean optional) { super(optional); Assert.hasText(contextPath, "Location must not be empty"); validatePath(contextPath); - this.secretBackendMetadata = KeyValueSecretBackendMetadata.create(contextPath); + this.secretBackendMetadata = KeyValueSecretBackendMetadata.create(contextPath, propertyTransformer); this.optional = optional; } + /** + * Create a new {@link VaultConfigLocation} instance. + * @param secretBackendMetadata the backend descriptor. + * @param optional if the resource is optional + */ public VaultConfigLocation(SecretBackendMetadata secretBackendMetadata, boolean optional) { Assert.notNull(secretBackendMetadata, "SecretBackendMetadata must not be null"); diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolverUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolverUnitTests.java index d0ae60c3f..ef4dc57f7 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolverUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultConfigDataLocationResolverUnitTests.java @@ -16,25 +16,19 @@ package org.springframework.cloud.vault.config; -import java.util.Arrays; -import java.util.List; - import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Test; + import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.context.config.ConfigDataLocation; import org.springframework.boot.context.config.ConfigDataLocationResolverContext; import org.springframework.boot.context.config.Profiles; import org.springframework.boot.context.properties.bind.Binder; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; @@ -44,6 +38,7 @@ * Unit tests for {@link VaultConfigDataLocationResolver}. * * @author Mark Paluch + * @author Jeffrey van der Laan */ public class VaultConfigDataLocationResolverUnitTests { @@ -106,10 +101,9 @@ public void shouldDiscoverContextualLocationsWithPrefix() { VaultConfigDataLocationResolver resolver = new VaultConfigDataLocationResolver(); List locations = resolver.resolveProfileSpecific(this.contextMock, - ConfigDataLocation.of("vault://my/context/path?prefix=myPrefix"), this.profilesMock); + ConfigDataLocation.of("vault://my/context/path?prefix=myPrefix."), this.profilesMock); assertThat(locations).hasSize(1); - assertThat(locations.get(0)).hasToString("VaultConfigLocation [path='my/context/path', optional=false]"); assertThat(locations.get(0).getSecretBackendMetadata().getPropertyTransformer() .transformProperties(Collections.singletonMap("key", "value"))).containsEntry("myPrefix.key", "value"); } @@ -123,7 +117,6 @@ public void shouldNotPrefixWhenPrefixIsEmpty() { ConfigDataLocation.of("vault://my/context/path?prefix="), this.profilesMock); assertThat(locations).hasSize(1); - assertThat(locations.get(0)).hasToString("VaultConfigLocation [path='my/context/path', optional=false]"); assertThat(locations.get(0).getSecretBackendMetadata().getPropertyTransformer() .transformProperties(Collections.singletonMap("key", "value"))).containsEntry("key", "value"); }