From 9bec609b8d7e6a3e86a777ff54adeb74de9d8e25 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 20 May 2020 15:26:43 +0200 Subject: [PATCH] Introduce profile override for Key Value backend. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now provide an override spring.cloud.vault.kv.profiles=… to specify application profiles that should be used for Vault access instead of Environment.getActiveProfiles(). Closes gh-385. --- .../src/main/asciidoc/spring-cloud-vault.adoc | 9 +- ...tBootstrapPropertySourceConfiguration.java | 6 +- .../VaultKeyValueBackendProperties.java | 28 ++++++ ...VaultKeyValueBackendPropertiesSupport.java | 8 ++ .../VaultPropertySourceLocatorSupport.java | 11 +-- ...ngVaultPropertySourceLocatorUnitTests.java | 9 +- ...SourceLocatorProfilesIntegrationTests.java | 87 +++++++++++++++++++ .../VaultPropertySourceLocatorUnitTests.java | 22 ++--- 8 files changed, 148 insertions(+), 32 deletions(-) create mode 100644 spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorProfilesIntegrationTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-vault.adoc b/docs/src/main/asciidoc/spring-cloud-vault.adoc index e5be7d62..830ce360 100644 --- a/docs/src/main/asciidoc/spring-cloud-vault.adoc +++ b/docs/src/main/asciidoc/spring-cloud-vault.adoc @@ -694,6 +694,11 @@ The application name is determined by the properties: * `spring.cloud.vault.application-name` * `spring.application.name` +The profiles are determined by the properties: + +* `spring.cloud.vault.kv.profiles` +* `spring.profiles.active` + Secrets can be obtained from other contexts within the key-value backend by adding their paths to the application name, separated by commas. For example, given the application name `usefulapp,mysql1,projectx/aws`, each of these folders will be used: @@ -706,7 +711,7 @@ No active profiles will skip accessing contexts with a profile name. Properties are exposed like they are stored (i.e. without additional prefixes). -NOTE: Spring Cloud Vault adds the `data/` context between the mount path and the actual context path. +NOTE: Spring Cloud Vault adds the `data/` context between the mount path and the actual context path depending on whether the mount uses the versioned key-value backend. ==== [source,yaml] @@ -718,6 +723,7 @@ spring.cloud.vault: profile-separator: '/' default-context: application application-name: my-app + profiles: local, cloud ---- ==== @@ -725,6 +731,7 @@ spring.cloud.vault: * `backend` sets the path of the secret mount to use * `default-context` sets the context name used by all applications * `application-name` overrides the application name for use in the key-value backend +* `profiles` overrides the active profiles for use in the key-value backend * `profile-separator` separates the profile name from the context in property sources with profiles NOTE: The key-value secret backend can be operated in versioned (v2) and non-versioned (v1) modes. diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java index 3ee7fdef..913735d2 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.cloud.vault.config; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -141,9 +140,8 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe continue; } - List contexts = KeyValueSecretBackendMetadata.buildContexts( - keyValueBackend, Arrays.asList(this.applicationContext - .getEnvironment().getActiveProfiles())); + List contexts = KeyValueSecretBackendMetadata + .buildContexts(keyValueBackend, keyValueBackend.getProfiles()); for (String context : contexts) { secretBackendConfigurer.add(KeyValueSecretBackendMetadata diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendProperties.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendProperties.java index a69f4a99..11348eb8 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendProperties.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendProperties.java @@ -16,6 +16,11 @@ package org.springframework.cloud.vault.config; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import javax.validation.constraints.NotEmpty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -63,6 +68,12 @@ public class VaultKeyValueBackendProperties */ private String applicationName = "application"; + /** + * List of active profiles. + * @since 3.0 + */ + private List profiles; + /** * Key-Value backend version. Currently supported versions are: *
    @@ -91,6 +102,10 @@ public class VaultKeyValueBackendProperties this.applicationName = springAppName; } } + + if (this.profiles == null) { + this.profiles = Arrays.asList(environment.getActiveProfiles()); + } } public boolean isEnabled() { @@ -113,6 +128,14 @@ public class VaultKeyValueBackendProperties return this.applicationName; } + @Override + public List getProfiles() { + if (this.profiles == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(new ArrayList<>(this.profiles)); + } + @Deprecated @DeprecatedConfigurationProperty( reason = "Backend version no longer required. The kv version is determined during secret retrieval") @@ -140,6 +163,10 @@ public class VaultKeyValueBackendProperties this.applicationName = applicationName; } + public void setProfiles(List profiles) { + this.profiles = profiles; + } + public void setBackendVersion(int backendVersion) { this.backendVersion = backendVersion; } @@ -153,6 +180,7 @@ public class VaultKeyValueBackendProperties sb.append(", defaultContext='").append(this.defaultContext).append('\''); sb.append(", profileSeparator='").append(this.profileSeparator).append('\''); sb.append(", applicationName='").append(this.applicationName).append('\''); + sb.append(", profiles='").append(this.profiles).append('\''); sb.append(", backendVersion=").append(this.backendVersion); sb.append(']'); return sb.toString(); diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendPropertiesSupport.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendPropertiesSupport.java index 685cc3d7..65bab9cc 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendPropertiesSupport.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultKeyValueBackendPropertiesSupport.java @@ -16,6 +16,8 @@ package org.springframework.cloud.vault.config; +import java.util.List; + /** * Interface declaring Key-Value configuration properties. * @@ -50,4 +52,10 @@ public interface VaultKeyValueBackendPropertiesSupport { */ String getApplicationName(); + /** + * @return the application profiles to use. + * @since 3.0 + */ + List getProfiles(); + } 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 1877881b..7efdfc8b 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 @@ -178,22 +178,15 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc SecretBackendMetadata accessor); private static class KeyValuePropertySourceLocatorConfiguration - implements EnvironmentAware, PropertySourceLocatorConfiguration { + implements PropertySourceLocatorConfiguration { private final VaultKeyValueBackendPropertiesSupport keyValueBackendProperties; - private Environment environment; - KeyValuePropertySourceLocatorConfiguration( VaultKeyValueBackendPropertiesSupport keyValueBackendProperties) { this.keyValueBackendProperties = keyValueBackendProperties; } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - @Override public Collection getSecretBackends() { @@ -201,7 +194,7 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc List contexts = KeyValueSecretBackendMetadata.buildContexts( this.keyValueBackendProperties, - Arrays.asList(this.environment.getActiveProfiles())); + this.keyValueBackendProperties.getProfiles()); List result = new ArrayList<>(contexts.size()); 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 f0fc3e88..0c8e0ae7 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 @@ -30,7 +30,6 @@ import org.springframework.vault.core.lease.domain.RequestedSecret; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * Unit tests for {@link LeasingVaultPropertySourceLocator}. @@ -48,12 +47,14 @@ public class LeasingVaultPropertySourceLocatorUnitTests { @Mock private SecretLeaseContainer secretLeaseContainer; + private VaultKeyValueBackendProperties properties = new VaultKeyValueBackendProperties(); + @Before public void before() { this.propertySourceLocator = new LeasingVaultPropertySourceLocator( - new VaultProperties(), VaultPropertySourceLocatorSupport - .createConfiguration(new VaultKeyValueBackendProperties()), + new VaultProperties(), + VaultPropertySourceLocatorSupport.createConfiguration(this.properties), this.secretLeaseContainer); } @@ -74,8 +75,6 @@ public class LeasingVaultPropertySourceLocatorUnitTests { @Test public void shouldLocatePropertySources() { - when(this.configurableEnvironment.getActiveProfiles()).thenReturn(new String[0]); - PropertySource propertySource = this.propertySourceLocator .locate(this.configurableEnvironment); diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorProfilesIntegrationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorProfilesIntegrationTests.java new file mode 100644 index 00000000..df185538 --- /dev/null +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/VaultPropertySourceLocatorProfilesIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018-2020 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 + * + * https://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.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.vault.util.IntegrationTestSupport; +import org.springframework.cloud.vault.util.VaultRule; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test incorporating loading secrets using + * {@code spring.cloud.vault.kv.profiles}. + * + * @author Mark Paluch + */ +@RunWith(SpringRunner.class) +@SpringBootTest( + classes = VaultPropertySourceLocatorProfilesIntegrationTests.TestApplication.class, + properties = { "spring.application.name=my-profiles-app", + "spring.cloud.vault.kv.profiles=hello, world", + "spring.cloud.vault.kv.default-context=" }) +@ActiveProfiles({ "other" }) +public class VaultPropertySourceLocatorProfilesIntegrationTests + extends IntegrationTestSupport { + + @Autowired + Environment environment; + + @BeforeClass + public static void beforeClass() { + + VaultRule vaultRule = new VaultRule(); + vaultRule.before(); + + vaultRule.prepare().getVaultOperations().write("secret/my-profiles-app/hello", + Collections.singletonMap("vault.hello", "true")); + + vaultRule.prepare().getVaultOperations().write("secret/my-profiles-app/world", + Collections.singletonMap("vault.world", "true")); + + vaultRule.prepare().getVaultOperations().write("secret/my-profiles-app/other", + Collections.singletonMap("vault.other", "true")); + } + + @Test + public void shouldContainValuesFromKvProfiles() { + assertThat(this.environment.getRequiredProperty("vault.hello")).isEqualTo("true"); + assertThat(this.environment.getRequiredProperty("vault.world")).isEqualTo("true"); + } + + @Test + public void shouldNotContainVaulesFromSpringProfiles() { + assertThat(this.environment.getProperty("vault.other")).isNull(); + } + + @SpringBootApplication + public static class TestApplication { + + } + +} 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 260bdc20..25782828 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 @@ -16,6 +16,8 @@ package org.springframework.cloud.vault.config; +import java.util.Arrays; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,7 +30,6 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; /** * Unit tests for {@link VaultPropertySourceLocator}. @@ -47,11 +48,13 @@ public class VaultPropertySourceLocatorUnitTests { @Mock private ConfigurableEnvironment configurableEnvironment; + private VaultKeyValueBackendProperties properties = new VaultKeyValueBackendProperties(); + @Before public void before() { this.propertySourceLocator = new VaultPropertySourceLocator(this.operations, - new VaultProperties(), VaultPropertySourceLocatorSupport - .createConfiguration(new VaultKeyValueBackendProperties())); + new VaultProperties(), + VaultPropertySourceLocatorSupport.createConfiguration(this.properties)); } @Test @@ -70,8 +73,6 @@ public class VaultPropertySourceLocatorUnitTests { @Test public void shouldLocateOnePropertySourceWithEmptyProfiles() { - when(this.configurableEnvironment.getActiveProfiles()).thenReturn(new String[0]); - PropertySource propertySource = this.propertySourceLocator .locate(this.configurableEnvironment); @@ -84,8 +85,7 @@ public class VaultPropertySourceLocatorUnitTests { @Test public void shouldLocatePropertySourcesForActiveProfilesInDefaultContext() { - when(this.configurableEnvironment.getActiveProfiles()) - .thenReturn(new String[] { "vermillion", "periwinkle" }); + this.properties.setProfiles(Arrays.asList("vermillion", "periwinkle")); PropertySource propertySource = this.propertySourceLocator .locate(this.configurableEnvironment); @@ -102,14 +102,12 @@ public class VaultPropertySourceLocatorUnitTests { VaultKeyValueBackendProperties backendProperties = new VaultKeyValueBackendProperties(); backendProperties.setApplicationName("wintermute"); + backendProperties.setProfiles(Arrays.asList("vermillion", "periwinkle")); this.propertySourceLocator = new VaultPropertySourceLocator(this.operations, new VaultProperties(), VaultPropertySourceLocatorSupport.createConfiguration(backendProperties)); - when(this.configurableEnvironment.getActiveProfiles()) - .thenReturn(new String[] { "vermillion", "periwinkle" }); - PropertySource propertySource = this.propertySourceLocator .locate(this.configurableEnvironment); @@ -126,14 +124,12 @@ public class VaultPropertySourceLocatorUnitTests { VaultKeyValueBackendProperties backendProperties = new VaultKeyValueBackendProperties(); backendProperties.setApplicationName("wintermute,straylight,icebreaker/armitage"); + backendProperties.setProfiles(Arrays.asList("vermillion", "periwinkle")); this.propertySourceLocator = new VaultPropertySourceLocator(this.operations, new VaultProperties(), VaultPropertySourceLocatorSupport.createConfiguration(backendProperties)); - when(this.configurableEnvironment.getActiveProfiles()) - .thenReturn(new String[] { "vermillion", "periwinkle" }); - PropertySource propertySource = this.propertySourceLocator .locate(this.configurableEnvironment);