From 6505bde94d64d5da1b2a3117a564fe8f18eba323 Mon Sep 17 00:00:00 2001 From: Jeffrey van der Laan Date: Fri, 18 Jun 2021 10:15:14 +0200 Subject: [PATCH] Add support for key prefixes in vault paths. See gh-488 Original pull request: gh-582. --- docs/src/main/asciidoc/config-data.adoc | 13 +++++++ .../VaultConfigDataLocationResolver.java | 17 +++++++- ...ltConfigDataLocationResolverUnitTests.java | 39 ++++++++++++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/config-data.adoc b/docs/src/main/asciidoc/config-data.adoc index a0486732..720b3134 100644 --- a/docs/src/main/asciidoc/config-data.adoc +++ b/docs/src/main/asciidoc/config-data.adoc @@ -50,6 +50,19 @@ 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 e827160f..e86c2ce7 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 @@ -37,6 +37,11 @@ import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.vault.core.util.PropertyTransformer; +import org.springframework.vault.core.util.PropertyTransformers; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** * {@link ConfigDataLocationResolver} for Vault resolving {@link VaultConfigLocation} @@ -113,7 +118,17 @@ public class VaultConfigDataLocationResolver implements ConfigDataLocationResolv contextPath = contextPath.substring(1); } - return Collections.singletonList(new VaultConfigLocation(contextPath, location.isOptional())); + 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())); + } } private static void registerVaultProperties(ConfigDataLocationResolverContext context) { 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 ee9751a3..d0ae60c3 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 @@ -19,15 +19,22 @@ 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; @@ -89,6 +96,36 @@ public class VaultConfigDataLocationResolverUnitTests { 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"); + } + + @Test + public void shouldDiscoverContextualLocationsWithPrefix() { + + VaultConfigDataLocationResolver resolver = new VaultConfigDataLocationResolver(); + + List locations = resolver.resolveProfileSpecific(this.contextMock, + 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"); + } + + @Test + public void shouldNotPrefixWhenPrefixIsEmpty() { + + VaultConfigDataLocationResolver resolver = new VaultConfigDataLocationResolver(); + + List locations = resolver.resolveProfileSpecific(this.contextMock, + 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"); } }