Introduce profile override for Key Value backend.

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.
This commit is contained in:
Mark Paluch
2020-05-20 15:26:43 +02:00
parent 9f4f8eda64
commit 9bec609b8d
8 changed files with 148 additions and 32 deletions

View File

@@ -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.

View File

@@ -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<String> contexts = KeyValueSecretBackendMetadata.buildContexts(
keyValueBackend, Arrays.asList(this.applicationContext
.getEnvironment().getActiveProfiles()));
List<String> contexts = KeyValueSecretBackendMetadata
.buildContexts(keyValueBackend, keyValueBackend.getProfiles());
for (String context : contexts) {
secretBackendConfigurer.add(KeyValueSecretBackendMetadata

View File

@@ -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<String> profiles;
/**
* Key-Value backend version. Currently supported versions are:
* <ul>
@@ -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<String> 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<String> 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();

View File

@@ -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<String> getProfiles();
}

View File

@@ -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<SecretBackendMetadata> getSecretBackends() {
@@ -201,7 +194,7 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
List<String> contexts = KeyValueSecretBackendMetadata.buildContexts(
this.keyValueBackendProperties,
Arrays.asList(this.environment.getActiveProfiles()));
this.keyValueBackendProperties.getProfiles());
List<SecretBackendMetadata> result = new ArrayList<>(contexts.size());

View File

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

View File

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

View File

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