From 6914dc89b1df04024095d2e6a43d79bf97baea1d Mon Sep 17 00:00:00 2001 From: Kaveh Shamsi Date: Wed, 9 Oct 2024 16:55:29 +0200 Subject: [PATCH] Uses label when searching secrets in [Spring]VaultEnvironmentRepository (#2460) * Uses label when searching secrets in [Spring]VaultEnvironmentRepository Signed-off-by: kvmw * Adds a feature flag to enable label in vault secret paths Signed-off-by: kvmw * Makes default-label in [Spring]VaultEnvironmentRepository configurable Signed-off-by: kvmw * When label flag is enabled, profile should always by included in vault key Signed-off-by: kvmw * Updates Vault docs Signed-off-by: kvmw * Switches to main as default label for vault Signed-off-by: kvmw --------- Signed-off-by: kvmw --- .../environment-repository/vault-backend.adoc | 24 +++ ...nfigDataOrderingVaultIntegrationTests.java | 45 ++-- .../AbstractVaultEnvironmentRepository.java | 107 ++++++---- .../VaultEnvironmentProperties.java | 27 +++ .../VaultEnvironmentRepositoryTests.java | 197 ++++++++++++++++-- ...SpringVaultEnvironmentRepositoryTests.java | 188 ++++++++++++++++- 6 files changed, 501 insertions(+), 87 deletions(-) diff --git a/docs/modules/ROOT/pages/server/environment-repository/vault-backend.adoc b/docs/modules/ROOT/pages/server/environment-repository/vault-backend.adoc index ef16a0df..ebbd6854 100644 --- a/docs/modules/ROOT/pages/server/environment-repository/vault-backend.adoc +++ b/docs/modules/ROOT/pages/server/environment-repository/vault-backend.adoc @@ -59,6 +59,12 @@ The following table describes configurable Vault properties: |defaultKey |application +|defaultLabel +|main (Only used when `enableLabel` is set to `true`) + +|enableLabel +|false + |profileSeparator |, @@ -158,6 +164,24 @@ Properties written to `secret/application` are available to <<_vault_server,all An application with the name, `myApp`, would have any properties written to `secret/myApp` and `secret/application` available to it. When `myApp` has the `dev` profile enabled, properties written to all of the above paths would be available to it, with properties in the first path in the list taking priority over the others. +[[enabling-serach-by-label]] +== Enabling Search by Label + +By default, Vault backend does not use the label when searching for secrets. You can change this by +setting the `enableLabel` feature flag to `true` and, optionally, setting the `defaultLabel`. +When `defaultLabel` is not provided `main` will be used. + +When `enableLabel` feature flag is on, the secrets in Vault should always have all three segments(application name, profile and label) in their paths. +So the example in previous section, with enabled feature flag, would be like : + +[source,sh] +---- +secret/myApp,dev,myLabel +secret/myApp,default,myLabel # default profile +secret/application,dev,myLabel # default application name +secret/application,default,myLabel # default application name and default profile. +---- + [[decrypting-vault-secrets]] == Decrypting Vault Secrets in Property Sources diff --git a/spring-cloud-config-sample/src/test/java/sample/ConfigDataOrderingVaultIntegrationTests.java b/spring-cloud-config-sample/src/test/java/sample/ConfigDataOrderingVaultIntegrationTests.java index 408c55b9..56eb4d6d 100644 --- a/spring-cloud-config-sample/src/test/java/sample/ConfigDataOrderingVaultIntegrationTests.java +++ b/spring-cloud-config-sample/src/test/java/sample/ConfigDataOrderingVaultIntegrationTests.java @@ -18,11 +18,11 @@ package sample; import java.io.IOException; -import org.json.JSONException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.Container.ExecResult; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; @@ -36,9 +36,10 @@ import org.springframework.test.util.TestSocketUtils; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration test for https://github.com/spring-cloud/spring-cloud-config/issues/1997 - * The error only occurs if a profile specific config imports is used, otherwise - * reordering does not take place. A profile specific config import is defined in + * Integration test for issue + * #1997 The + * error only occurs if a profile specific config imports is used, otherwise reordering + * does not take place. A profile specific config import is defined in * vaultordering/client-dev.yml */ @Testcontainers @@ -46,27 +47,26 @@ public class ConfigDataOrderingVaultIntegrationTests { private static final int configServerPort = TestSocketUtils.findAvailableTcpPort(); - private static final int configClientPort = TestSocketUtils.findAvailableTcpPort(); - private static ConfigurableApplicationContext client; private static ConfigurableApplicationContext server; @Container - public static VaultContainer vaultContainer = new VaultContainer<>(DockerImageName.parse("vault:1.13.3")) + public static VaultContainer vaultContainer = new VaultContainer<>(DockerImageName.parse("vault:1.13.3")) .withVaultToken("my-root-token") .withClasspathResourceMapping("vaultordering/vault_test_policy.txt", "/tmp/vault_test_policy.txt", BindMode.READ_ONLY); @BeforeAll - public static void startConfigServer() throws IOException, InterruptedException, JSONException { + public static void startConfigServer() throws IOException, InterruptedException { server = SpringApplication.run(TestConfigServerApplication.class, "--spring.config.location=classpath:/vaultordering/", "--spring.config.name=server", "--server.port=" + configServerPort, "--spring.cloud.config.server.vault.port=" + vaultContainer.getFirstMappedPort()); - execInVault("vault", "kv", "put", "secret/client-app,dev", "my.prop=vaultdev"); - execInVault("vault", "kv", "put", "secret/client-app", "my.prop=vault"); + execInVault("vault", "kv", "put", "secret/client-app,dev", "my.prop=value-in-dev"); + execInVault("vault", "kv", "put", "secret/client-app,prod", "my.prop=value-in-prod"); + execInVault("vault", "kv", "put", "secret/client-app", "my.prop=default-value"); } @@ -81,22 +81,35 @@ public class ConfigDataOrderingVaultIntegrationTests { } @Test - void propertyFromVaultIsUsed() { - client = SpringApplication.run(TestConfigServerApplication.class, "--server.port=" + configClientPort, + void profileSpecificPropertyFromVaultIsUsed() { + client = SpringApplication.run(TestConfigServerApplication.class, + "--server.port=" + TestSocketUtils.findAvailableTcpPort(), "--spring.config.location=classpath:/vaultordering/", "--spring.config.name=client", "--spring.profiles.active=dev", "--spring.application.name=client-app", "--spring.cloud.config.enabled=true", "--spring.cloud.config.server.enabled=false", "--config.server.port=" + configServerPort); - assertThat(client.getEnvironment().getProperty("my.prop")).isEqualTo("vaultdev"); + assertThat(client.getEnvironment().getProperty("my.prop")).isEqualTo("value-in-dev"); } - private static String execInVault(String... command) throws IOException, InterruptedException { - org.testcontainers.containers.Container.ExecResult execResult = vaultContainer.execInContainer(command); + @Test + void profileSpecificPropertyFromVaultIsUsedInCorrectOrder() { + client = SpringApplication.run(TestConfigServerApplication.class, + "--server.port=" + TestSocketUtils.findAvailableTcpPort(), + "--spring.config.location=classpath:/vaultordering/", "--spring.config.name=client", + "--spring.profiles.active=dev,prod", "--spring.application.name=client-app", + "--spring.cloud.config.enabled=true", "--spring.cloud.config.server.enabled=false", + "--config.server.port=" + configServerPort); + + assertThat(client.getEnvironment().getProperty("my.prop")).isEqualTo("value-in-prod"); + + } + + private static void execInVault(String... command) throws IOException, InterruptedException { + ExecResult execResult = vaultContainer.execInContainer(command); assertThat(execResult.getExitCode()).isZero(); assertThat(execResult.getStderr()).isEmpty(); - return execResult.getStdout(); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java index 7d47d643..1917e19d 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java @@ -16,11 +16,12 @@ package org.springframework.cloud.config.server.environment; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Properties; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.constraints.NotEmpty; @@ -33,6 +34,7 @@ import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.core.Ordered; import org.springframework.core.io.ByteArrayResource; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER; @@ -46,7 +48,9 @@ import static org.springframework.cloud.config.client.ConfigClientProperties.STA */ public abstract class AbstractVaultEnvironmentRepository implements EnvironmentRepository, Ordered { - private static Log log = LogFactory.getLog(AbstractVaultEnvironmentRepository.class); + private static final String DEFAULT_PROFILE = "default"; + + private static final Log log = LogFactory.getLog(AbstractVaultEnvironmentRepository.class); // TODO: move to watchState:String on findOne? protected final ObjectProvider request; @@ -65,12 +69,18 @@ public abstract class AbstractVaultEnvironmentRepository implements EnvironmentR @NotEmpty protected String profileSeparator; + protected final boolean enableLabel; + + protected final String defaultLabel; + protected int order; public AbstractVaultEnvironmentRepository(ObjectProvider request, EnvironmentWatch watch, VaultEnvironmentProperties properties) { this.defaultKey = properties.getDefaultKey(); this.profileSeparator = properties.getProfileSeparator(); + this.enableLabel = properties.isEnableLabel(); + this.defaultLabel = properties.getDefaultLabel(); this.order = properties.getOrder(); this.request = request; this.watch = watch; @@ -78,24 +88,32 @@ public abstract class AbstractVaultEnvironmentRepository implements EnvironmentR @Override public Environment findOne(String application, String profile, String label) { - String[] profiles = StringUtils.commaDelimitedListToStringArray(profile); - List scrubbedProfiles = scrubProfiles(profiles); + if (ObjectUtils.isEmpty(profile)) { + profile = DEFAULT_PROFILE; + } + if (ObjectUtils.isEmpty(label)) { + label = defaultLabel; + } - List keys = findKeys(application, scrubbedProfiles); + var environment = new Environment(application, split(profile), label, null, getWatchState()); - Environment environment = new Environment(application, profiles, label, null, getWatchState()); + var profiles = normalize(profile, DEFAULT_PROFILE); + var applications = normalize(application, this.defaultKey); - for (String key : keys) { - // read raw 'data' key from vault - String data = read(key); - if (data != null) { - // data is in json format of which, yaml is a superset, so parse - final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); - yaml.setResources(new ByteArrayResource(data.getBytes())); - Properties properties = yaml.getObject(); + for (String prof : profiles) { + for (String app : applications) { + var key = vaultKey(app, prof, label); + // read raw 'data' key from vault + String data = read(key); + if (data != null) { + // data is in json format of which, yaml is a superset, so parse + var yaml = new YamlPropertiesFactoryBean(); + yaml.setResources(new ByteArrayResource(data.getBytes())); + var properties = yaml.getObject(); - if (!properties.isEmpty()) { - environment.add(new PropertySource("vault:" + key, properties)); + if (properties != null && !properties.isEmpty()) { + environment.add(new PropertySource("vault:" + key, properties)); + } } } } @@ -105,6 +123,22 @@ public abstract class AbstractVaultEnvironmentRepository implements EnvironmentR protected abstract String read(String key); + private String vaultKey(String application, String profile, String label) { + var key = application; + if (this.enableLabel) { + // always append profile to the key, if flag is enabled. + key += this.profileSeparator + profile; + // always append label to the key, if flag is enabled. + key += this.profileSeparator + label; + } + else if (!DEFAULT_PROFILE.equals(profile)) { + // default profile should not be included in the key, if flag is not enabled. + key += this.profileSeparator + profile; + } + + return key; + } + private String getWatchState() { HttpServletRequest servletRequest = this.request.getIfAvailable(); if (servletRequest != null) { @@ -120,35 +154,22 @@ public abstract class AbstractVaultEnvironmentRepository implements EnvironmentR return null; } - private List findKeys(String application, List profiles) { - List keys = new ArrayList<>(); + /** + * Splits the comma delimited items and returns the reversed distinct items with given + * default item at the end. + */ + private List normalize(String commaDelimitedItems, String defaultItem) { + var items = Stream.concat(Stream.of(defaultItem), Arrays.stream(split(commaDelimitedItems))) + .distinct() + .filter(Predicate.not(ObjectUtils::isEmpty)) + .collect(Collectors.toList()); - if (StringUtils.hasText(this.defaultKey) && !this.defaultKey.equals(application)) { - keys.add(this.defaultKey); - addProfiles(keys, this.defaultKey, profiles); - } - - // application may have comma-separated list of names - String[] applications = StringUtils.commaDelimitedListToStringArray(application); - for (String app : applications) { - keys.add(app); - addProfiles(keys, app, profiles); - } - - Collections.reverse(keys); - return keys; + Collections.reverse(items); + return items; } - private List scrubProfiles(String[] profiles) { - List scrubbedProfiles = new ArrayList<>(Arrays.asList(profiles)); - scrubbedProfiles.remove("default"); - return scrubbedProfiles; - } - - private void addProfiles(List contexts, String baseContext, List profiles) { - for (String profile : profiles) { - contexts.add(baseContext + this.profileSeparator + profile); - } + private String[] split(String str) { + return StringUtils.commaDelimitedListToStringArray(str); } public void setDefaultKey(String defaultKey) { diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java index f5413cf3..2e25ee90 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java @@ -101,6 +101,17 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp */ private String token; + /** + * Flag to indicate that the repository should use 'label' as well as + * 'application-name' and 'profile', for vault secrets. By default, the vault secrets + * are expected to be in 'application-name,profile' path. When this flag enabled, they + * are expected to be in `application-name,profile,label' path. To maintain + * compatibility this flag is not enabled by default. + */ + private boolean enableLabel = false; + + private String defaultLabel = "main"; + private AppRoleProperties appRole = new AppRoleProperties(); private AwsEc2Properties awsEc2 = new AwsEc2Properties(); @@ -229,6 +240,22 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp this.token = token; } + public boolean isEnableLabel() { + return enableLabel; + } + + public void setEnableLabel(boolean enableLabel) { + this.enableLabel = enableLabel; + } + + public String getDefaultLabel() { + return defaultLabel; + } + + public void setDefaultLabel(String defaultLabel) { + this.defaultLabel = defaultLabel; + } + public AppRoleProperties getAppRole() { return this.appRole; } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java index c5711155..a68f2211 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryTests.java @@ -23,7 +23,6 @@ import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -57,12 +56,8 @@ public class VaultEnvironmentRepositoryTests { @SuppressWarnings("unchecked") ArgumentCaptor> requestHeaderCaptor = ArgumentCaptor.forClass(HttpEntity.class); - @BeforeEach - public void init() { - } - @Test - public void testFindOneNoDefaultKey() { + public void findOneWithNoDefaultKey() { stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); @@ -71,14 +66,33 @@ public class VaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); assertThat(requestHeaderCaptor.getValue().getHeaders()).containsEntry("X-Vault-Token", List.of("token")); } @Test - public void testBackendWithSlashes() { + public void findOneWithEmptyDefaultKey() { + stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setDefaultKey(""); + + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, "my-label"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(1); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + } + + @Test + public void findOneWithSlashesInBackend() { stubRestTemplate("foo/bar/secret/myapp", toEntityResponse("foo", "bar")); stubRestTemplate("foo/bar/secret/application", toEntityResponse("def-foo", "def-bar")); @@ -90,12 +104,14 @@ public class VaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testFindOneDefaultKeySetAndDifferentToApplication() { + public void findOneWithDefaultKeySet() { stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); stubRestTemplate("secret/mydefaultkey", toEntityResponse("def-foo", "def-bar")); @@ -107,12 +123,14 @@ public class VaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:mydefaultkey"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testFindOneDefaultKeySetAndDifferentToMultipleApplications() { + public void findOneWithDefaultKeyAndMultipleApplicationNames() { stubRestTemplate("secret/myapp", toEntityResponse("myapp-foo", "myapp-bar")); stubRestTemplate("secret/yourapp", toEntityResponse("yourapp-foo", "yourapp-bar")); stubRestTemplate("secret/mydefaultkey", toEntityResponse("def-foo", "def-bar")); @@ -126,28 +144,82 @@ public class VaultEnvironmentRepositoryTests { assertThat(e.getPropertySources().size()).isEqualTo(3); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("yourapp-foo", "yourapp-bar")); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:yourapp"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("myapp-foo", "myapp-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:mydefaultkey"); } @Test - public void testFindOneDefaultKeySetAndEqualToApplication() { + public void findOneWithDefaultKeySetToApplicationName() { stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); var properties = new VaultEnvironmentProperties(); properties.setDefaultKey("myapp"); - var e = vaultEnvironmentRepository(properties).findOne("myapp", null, null); + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, "lbl"); assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(1); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); } @Test - public void missingConfigToken() { + public void findOneWithProfile() { + stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/myapp,my-profile", toEntityResponse("pro-foo", "pro-bar")); + stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); + stubRestTemplate("secret/application,my-profile", toEntityResponse("def-pro-foo", "def-pro-bar")); + + var e = vaultEnvironmentRepository().findOne("myapp", "my-profile", "lbl"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(4); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,my-profile"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("pro-foo", "pro-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,my-profile"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pro-foo", "def-pro-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithMultipleProfiles() { + stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/myapp,pr1", toEntityResponse("pr1-foo", "pr1-bar")); + stubRestTemplate("secret/myapp,pr2", toEntityResponse("pr2-foo", "pr2-bar")); + stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); + stubRestTemplate("secret/application,pr1", toEntityResponse("def-pr1-foo", "def-pr1-bar")); + stubRestTemplate("secret/application,pr2", toEntityResponse("def-pr2-foo", "def-pr2-bar")); + + var e = vaultEnvironmentRepository().findOne("myapp", "pr1,pr2", null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(6); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,pr2"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("pr2-foo", "pr2-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,pr2"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pr2-foo", "def-pr2-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp,pr1"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("pr1-foo", "pr1-bar")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application,pr1"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-pr1-foo", "def-pr1-bar")); + assertThat(e.getPropertySources().get(4).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(4).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(5).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(5).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWhenConfigTokenIsMissing() { ConfigTokenProvider nullTokenProvider = () -> null; Assertions.assertThatThrownBy(() -> vaultEnvironmentRepository(nullTokenProvider).findOne("myapp", null, null)) @@ -155,25 +227,28 @@ public class VaultEnvironmentRepositoryTests { } @Test - public void testVaultVersioning() { + public void findOneWithVaultVersioning() { stubRestTemplate("secret/data/myapp", toEntityResponse("data", Map.of("foo", "bar"))); stubRestTemplate("secret/data/application", toEntityResponse("data", Map.of("def-foo", "def-bar"))); var properties = new VaultEnvironmentProperties(); properties.setKvVersion(2); - var e = vaultEnvironmentRepository(properties).findOne("myapp", null, null); + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, "label"); assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); assertThat(requestHeaderCaptor.getValue().getHeaders()).containsEntry("X-Vault-Token", List.of("token")); } @Test - public void testVaultKV2WithPath2Key() { + public void findOneWithVaultKV2WithPath2Key() { stubRestTemplate("secret/data/myorg/myapp", toEntityResponse("data", Map.of("foo", "bar"))); stubRestTemplate("secret/data/myorg/application", toEntityResponse("data", Map.of("def-foo", "def-bar"))); @@ -181,26 +256,112 @@ public class VaultEnvironmentRepositoryTests { properties.setKvVersion(2); properties.setPathToKey("myorg"); - var e = vaultEnvironmentRepository(properties).findOne("myapp", null, null); + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, "lbl"); assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testNamespaceHeaderSent() { + public void findOneWithNamespaceHeaderSent() { stubRestTemplate("secret/myapp", toEntityResponse("foo", "bar")); stubRestTemplate("secret/application", toEntityResponse("def-foo", "def-bar")); var properties = new VaultEnvironmentProperties(); properties.setNamespace("mynamespace"); - vaultEnvironmentRepository(properties).findOne("myapp", null, null); + vaultEnvironmentRepository(properties).findOne("myapp", null, "lbl"); assertThat(requestHeaderCaptor.getValue().getHeaders()).containsEntry("X-Vault-Namespace", List.of("mynamespace")); + assertThat(requestHeaderCaptor.getValue().getHeaders()).containsEntry("X-Vault-Token", List.of("token")); + } + + @Test + public void findOneWithDefaultLabelWhenLabelEnabled() { + stubRestTemplate("secret/myapp,default,main", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/application,default,main", toEntityResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,main"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,default,main"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithCustomDefaultLabelWhenLabelEnabled() { + stubRestTemplate("secret/myapp,default,custom", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/application,default,custom", toEntityResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + properties.setDefaultLabel("custom"); + + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,custom"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,default,custom"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithCustomLabelWhenLabelEnabled() { + stubRestTemplate("secret/myapp,default,my-label", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/application,default,my-label", toEntityResponse(Map.of())); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + properties.setDefaultLabel("custom"); + + var e = vaultEnvironmentRepository(properties).findOne("myapp", null, "my-label"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(1); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,my-label"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + } + + @Test + public void findOneWithCustomLabelAndProfileWhenLabelEnabled() { + stubRestTemplate("secret/myapp,default,my-label", toEntityResponse("foo", "bar")); + stubRestTemplate("secret/myapp,pr1,my-label", toEntityResponse("pr1-foo", "pr1-bar")); + stubRestTemplate("secret/application,default,my-label", toEntityResponse("def-foo", "def-bar")); + stubRestTemplate("secret/application,pr1,my-label", toEntityResponse("def-pr1-foo", "def-pr1-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + + var e = vaultEnvironmentRepository(properties).findOne("myapp", "pr1", "my-label"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources().size()).isEqualTo(4); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,pr1,my-label"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("pr1-foo", "pr1-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,pr1,my-label"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pr1-foo", "def-pr1-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp,default,my-label"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application,default,my-label"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } private VaultEnvironmentRepository vaultEnvironmentRepository() { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java index 406aa612..9e3987c8 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java @@ -45,17 +45,17 @@ public class SpringVaultEnvironmentRepositoryTests { private final VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class); @Test - public void testFindOneNoDefaultKey() { + public void findOneNoDefaultKey() { defaultKeyTest("", 2); } @Test - public void testPathKey() { + public void findOneWithPathKey() { defaultKeyTest("mypath", 2); } @Test - public void testPathKeyNotUsedWithVersionOne() { + public void findOneWithPathKeyNotUsedWithVersionOne() { defaultKeyTest("mypath", 1); } @@ -77,12 +77,14 @@ public class SpringVaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testBackendWithSlashes() { + public void findOneWithSlashesInBackend() { when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); @@ -94,29 +96,50 @@ public class SpringVaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testFindOneDefaultKeySetAndDifferentToApplication() { + public void findOneWithDefaultKeySet() { when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); when(keyValueTemplate.get("mydefaultkey")).thenReturn(withVaultResponse("def-foo", "def-bar")); var properties = new VaultEnvironmentProperties(); properties.setDefaultKey("mydefaultkey"); - var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, null); + var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, "label"); assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:mydefaultkey"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testFindOneDefaultKeySetAndDifferentToMultipleApplications() { + public void findOneWithEmptyDefaultKey() { + when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setDefaultKey(""); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, "label"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(1); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + } + + @Test + public void findOneWithDefaultKeyAndMultipleApplicationNames() { when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("myapp-foo", "myapp-bar")); when(keyValueTemplate.get("yourapp")).thenReturn(withVaultResponse("yourapp-foo", "yourapp-bar")); when(keyValueTemplate.get("mydefaultkey")).thenReturn(withVaultResponse("def-foo", "def-bar")); @@ -124,18 +147,21 @@ public class SpringVaultEnvironmentRepositoryTests { var properties = new VaultEnvironmentProperties(); properties.setDefaultKey("mydefaultkey"); - var e = springVaultEnvironmentRepository(properties).findOne("myapp,yourapp", null, null); + var e = springVaultEnvironmentRepository(properties).findOne("myapp,yourapp", null, "lbl"); assertThat(e.getName()).isEqualTo("myapp,yourapp"); assertThat(e.getPropertySources()).hasSize(3); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:yourapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("yourapp-foo", "yourapp-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("myapp-foo", "myapp-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:mydefaultkey"); assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } @Test - public void testFindOneDefaultKeySetAndEqualToApplication() { + public void findOneWithDefaultKeySetToApplicationName() { when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); @@ -147,11 +173,66 @@ public class SpringVaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources()).hasSize(1); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); } @Test - public void testVaultVersioning() { + public void findOneWithProfile() { + when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("myapp,pr1")).thenReturn(withVaultResponse("foo-pr1", "bar-pr1")); + when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); + when(keyValueTemplate.get("application,pr1")).thenReturn(withVaultResponse("def-pr1-foo", "def-pr1-bar")); + + var properties = new VaultEnvironmentProperties(); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", "pr1", "lbl"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(4); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,pr1"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo-pr1", "bar-pr1")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,pr1"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pr1-foo", "def-pr1-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithMultipleProfiles() { + when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("myapp,pr1")).thenReturn(withVaultResponse("foo-pr1", "bar-pr1")); + when(keyValueTemplate.get("myapp,pr2")).thenReturn(withVaultResponse("foo-pr2", "bar-pr2")); + when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); + when(keyValueTemplate.get("application,pr1")).thenReturn(withVaultResponse("def-pr1-foo", "def-pr1-bar")); + when(keyValueTemplate.get("application,pr2")).thenReturn(withVaultResponse("def-pr2-foo", "def-pr2-bar")); + + var properties = new VaultEnvironmentProperties(); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", "pr1,pr2", "lbl"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(6); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,pr2"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo-pr2", "bar-pr2")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,pr2"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pr2-foo", "def-pr2-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp,pr1"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("foo-pr1", "bar-pr1")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application,pr1"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-pr1-foo", "def-pr1-bar")); + assertThat(e.getPropertySources().get(4).getName()).isEqualTo("vault:myapp"); + assertThat(e.getPropertySources().get(4).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(5).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(5).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithVaultVersioning() { when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar")); when(keyValueTemplate.get("application")).thenReturn(withVaultResponse("def-foo", "def-bar")); @@ -163,7 +244,94 @@ public class SpringVaultEnvironmentRepositoryTests { assertThat(e.getName()).isEqualTo("myapp"); assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp"); assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithDefaultLabelWhenLabelEnabled() { + when(keyValueTemplate.get("myapp,default,main")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("application,default,main")).thenReturn(withVaultResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,main"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,default,main"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithProfileAndDefaultLabelWhenLabelEnabled() { + when(keyValueTemplate.get("myapp,default,main")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("myapp,pr1,main")).thenReturn(withVaultResponse("pr1-foo", "pr1-bar")); + when(keyValueTemplate.get("application,default,main")).thenReturn(withVaultResponse("def-foo", "def-bar")); + when(keyValueTemplate.get("application,pr1,main")).thenReturn(withVaultResponse("def-pr1-foo", "def-pr1-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", "pr1", null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(4); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,pr1,main"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("pr1-foo", "pr1-bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,pr1,main"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-pr1-foo", "def-pr1-bar")); + assertThat(e.getPropertySources().get(2).getName()).isEqualTo("vault:myapp,default,main"); + assertThat(e.getPropertySources().get(2).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(3).getName()).isEqualTo("vault:application,default,main"); + assertThat(e.getPropertySources().get(3).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithCustomDefaultLabelWhenLabelEnabled() { + when(keyValueTemplate.get("myapp,default,custom")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("application,default,custom")).thenReturn(withVaultResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + properties.setDefaultLabel("custom"); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, null); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,custom"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,default,custom"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); + } + + @Test + public void findOneWithCustomLabelWhenLabelEnabled() { + when(keyValueTemplate.get("myapp,default,my-label")).thenReturn(withVaultResponse("foo", "bar")); + when(keyValueTemplate.get("application,default,my-label")).thenReturn(withVaultResponse("def-foo", "def-bar")); + + var properties = new VaultEnvironmentProperties(); + properties.setEnableLabel(true); + properties.setDefaultLabel("custom"); + + var e = springVaultEnvironmentRepository(properties).findOne("myapp", null, "my-label"); + + assertThat(e.getName()).isEqualTo("myapp"); + + assertThat(e.getPropertySources()).hasSize(2); + assertThat(e.getPropertySources().get(0).getName()).isEqualTo("vault:myapp,default,my-label"); + assertThat(e.getPropertySources().get(0).getSource()).isEqualTo(Map.of("foo", "bar")); + assertThat(e.getPropertySources().get(1).getName()).isEqualTo("vault:application,default,my-label"); + assertThat(e.getPropertySources().get(1).getSource()).isEqualTo(Map.of("def-foo", "def-bar")); } private SpringVaultEnvironmentRepository springVaultEnvironmentRepository(VaultEnvironmentProperties properties) {