Add support for key prefixes in vault paths.

See gh-488
Original pull request: gh-582.
This commit is contained in:
Jeffrey van der Laan
2021-06-18 10:15:14 +02:00
committed by Mark Paluch
parent d4de4b6416
commit 6505bde94d
3 changed files with 67 additions and 2 deletions

View File

@@ -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. <<vault.config.fail-fast>> 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

View File

@@ -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) {

View File

@@ -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<VaultConfigLocation> 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<VaultConfigLocation> 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");
}
}