Add versioned key-value backend support.

We now provide configuration support for the versioned key-value backend introduced with Vault 0.10.0. This backend uses configuration properties prefixed with spring.cloud.vault.kv and defaults to the secret mount path. It resembles configuration properties from the generic secret backend. Using the versioned key-value backend requires disabling the generic secret backend (spring.cloud.vault.generic.enabled=false).

The versioned key-value backend can be configured programmatically through SecretBackendConfigurer.add(KeyValueSecretBackendMetadata.create(…)) which will add data path segments and unwrap nested data elements from the response.

See gh-209.
This commit is contained in:
Mark Paluch
2018-04-24 16:49:04 +02:00
parent 37dd7711ec
commit 8c297dc667
9 changed files with 626 additions and 106 deletions

View File

@@ -189,8 +189,8 @@ The following scenarios are supported along the required configuration details:
.Configuration
|===
| *Method* | *RoleId* | *SecretId*| *RoleName* | *Token*
| Provided RoleId/SecretId | Provided | Provided | |
| Provided RoleId without SecretId | Provided | | |
| Provided RoleId/SecretId | Provided | Provided | |
| Provided RoleId without SecretId | Provided | | |
| Provided RoleId, Pull SecretId | Provided | Provided | Provided | Provided
| Pull RoleId, provided SecretId | | Provided | Provided | Provided
| Full Pull Mode | | | Provided | Provided
@@ -203,20 +203,20 @@ The following scenarios are supported along the required configuration details:
|===
| *RoleId* | *SecretId* | *Supported*
| Provided | Provided | ✅
| Provided | Pull | ✅
| Provided | Wrapped | ✅
| Provided | Absent | ✅
| Pull | Provided | ✅
| Pull | Pull | ✅
| Pull | Wrapped | ❌
| Pull | Absent | ❌
| Wrapped | Provided | ✅
| Wrapped | Pull | ❌
| Wrapped | Wrapped | ✅
| Wrapped | Absent | ❌
| Provided | Pull | ✅
| Provided | Wrapped | ✅
| Provided | Absent | ✅
| Pull | Provided | ✅
| Pull | Pull | ✅
| Pull | Wrapped | ❌
| Pull | Absent | ❌
| Wrapped | Provided | ✅
| Wrapped | Pull | ❌
| Wrapped | Wrapped | ✅
| Wrapped | Absent | ❌
|===
NOTE: You can use still all combinations of push/pull/wrapped modes by providing a configured `AppRoleAuthentication` bean within the boostrap context. Spring Cloud Vault cannot derive all possible AppRole combinations from the configuration properties.
NOTE: You can use still all combinations of push/pull/wrapped modes by providing a configured `AppRoleAuthentication` bean within the boostrap context. Spring Cloud Vault cannot derive all possible AppRole combinations from the configuration properties.
.bootstrap.yml with all AppRole authentication properties
====
@@ -462,7 +462,7 @@ spring.cloud.vault:
* `role` sets the Role.
* `service-account-token-file` sets the location of the file containing the Kubernetes Service Account Token. Defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
See also:
See also:
* https://www.vaultproject.io/docs/auth/kubernetes.html[Vault Documentation: Kubernetes]
* https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Documentation: Configure Service Accounts for Pods]
@@ -470,6 +470,7 @@ See also:
[[vault.config.backends]]
== Secret Backends
[[vault.config.backends.kv]]
[[vault.config.backends.generic]]
=== Generic Backend
@@ -492,7 +493,7 @@ The application name is determined by the properties:
* `spring.cloud.vault.application-name`
* `spring.application.name`
Secrets can be obtained from other folders within the generic backend by adding their
Secrets can be obtained from other contexts within the generic 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:
@@ -526,7 +527,71 @@ config usage
* `profile-separator` separates the profile name from the context in
property sources with profiles
See also: https://www.vaultproject.io/docs/secrets/generic/index.html[Vault Documentation: Using the generic secret backend]
NOTE: The key-value secret backend can be operated in versioned (v2) and non-versioned (v1) modes. Depending on the mode of operation, a different API is required to access secrets. Make sure to enable `generic` secret backend usage for non-versioned key-value backends and `kv` secret backend usage for versioned key-value backends.
See also: https://www.vaultproject.io/docs/secrets/kv/kv-v1.html[Vault Documentation: Using the KV Secrets Engine - Version 1 (generic secret backend)]
[[vault.config.backends.kv.versioned]]
=== Versioned Key-Value Backend
Spring Cloud Vault supports the versioned Key-Value secret
backend. The key-value backend allows storage of arbitrary
values as key-value store. A single context can store one or many
key-value tuples. Contexts can be organized hierarchically.
Spring Cloud Vault allows using the Application name
and a default context name (`application`) in combination with active
profiles.
----
/secret/{application}/{profile}
/secret/{application}
/secret/{default-context}/{profile}
/secret/{default-context}
----
The application name is determined by the properties:
* `spring.cloud.vault.kv.application-name`
* `spring.cloud.vault.application-name`
* `spring.application.name`
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:
* `/secret/usefulapp`
* `/secret/mysql1`
* `/secret/projectx/aws`
Spring Cloud Vault adds all active profiles to the list of possible context paths.
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.
====
[source,yaml]
----
spring.cloud.vault:
kv:
enabled: true
backend: secret
profile-separator: '/'
default-context: application
application-name: my-app
----
====
* `enabled` setting this value to `false` disables the secret backend
config usage
* `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 generic 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. Depending on the mode of operation, a different API is required to access secrets. Make sure to enable `generic` secret backend usage for non-versioned key-value backends and `kv` secret backend usage for versioned key-value backends.
See also: https://www.vaultproject.io/docs/secrets/kv/kv-v2.html[Vault Documentation: Using the KV Secrets Engine - Version 2 (versioned key-value backend)]
[[vault.config.backends.consul]]
=== Consul

View File

@@ -15,30 +15,20 @@
*/
package org.springframework.cloud.vault.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link SecretBackendMetadata} for the {@code generic} secret backend.
*
* @author Mark Paluch
*/
public class GenericSecretBackendMetadata extends SecretBackendMetadataSupport
implements SecretBackendMetadata {
private final String path;
public class GenericSecretBackendMetadata extends KeyValueSecretBackendMetadata implements
SecretBackendMetadata {
private GenericSecretBackendMetadata(String path) {
Assert.hasText(path, "Secret backend path must not be empty");
this.path = path;
super(path);
}
/**
@@ -53,8 +43,7 @@ public class GenericSecretBackendMetadata extends SecretBackendMetadataSupport
*/
public static SecretBackendMetadata create(String secretBackendPath, String key) {
Assert.hasText(secretBackendPath,
"Secret backend path must not be null or empty");
Assert.hasText(secretBackendPath, "Secret backend path must not be null or empty");
Assert.hasText(key, "Key must not be null or empty");
return create(String.format("%s/%s", secretBackendPath, key));
@@ -73,40 +62,17 @@ public class GenericSecretBackendMetadata extends SecretBackendMetadataSupport
return new GenericSecretBackendMetadata(path);
}
@Override
public String getPath() {
return path;
}
/**
* Build a list of context paths from application name and the active profile names.
* Application name and profiles support multiple (comma-separated) values.
*
* @param genericBackendProperties
* @param properties
* @param profiles active application profiles.
* @return list of context paths.
*/
public static List<String> buildContexts(
VaultGenericBackendProperties genericBackendProperties,
public static List<String> buildContexts(VaultGenericBackendProperties properties,
List<String> profiles) {
String appName = genericBackendProperties.getApplicationName();
Set<String> contexts = new LinkedHashSet<>();
String defaultContext = genericBackendProperties.getDefaultContext();
contexts.addAll(buildContexts(defaultContext, profiles,
genericBackendProperties.getProfileSeparator()));
for (String applicationName : StringUtils.commaDelimitedListToSet(appName)) {
contexts.addAll(buildContexts(applicationName, profiles,
genericBackendProperties.getProfileSeparator()));
}
List<String> result = new ArrayList<>(contexts);
Collections.reverse(result);
return result;
return KeyValueSecretBackendMetadata.buildContexts(properties, profiles);
}
/**
@@ -123,30 +89,7 @@ public class GenericSecretBackendMetadata extends SecretBackendMetadataSupport
*/
public static List<String> buildContexts(String applicationName,
List<String> profiles, String profileSeparator) {
List<String> contexts = new ArrayList<>();
if (!StringUtils.hasText(applicationName)) {
return contexts;
}
if (!contexts.contains(applicationName)) {
contexts.add(applicationName);
}
for (String profile : profiles) {
if (!StringUtils.hasText(profile)) {
continue;
}
String contextName = applicationName + profileSeparator + profile.trim();
if (!contexts.contains(contextName)) {
contexts.add(contextName);
}
}
return contexts;
return KeyValueSecretBackendMetadata.buildContexts(applicationName, profiles,
profileSeparator);
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2018 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
*
* http://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.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.vault.core.util.PropertyTransformer;
import org.springframework.vault.core.util.PropertyTransformers;
/**
* {@link SecretBackendMetadata} for the {@code kv} (key-value) secret backend.
*
* @author Mark Paluch
* @since 2.0
*/
public class KeyValueSecretBackendMetadata extends SecretBackendMetadataSupport implements
SecretBackendMetadata {
private final String path;
private final PropertyTransformer propertyTransformer;
KeyValueSecretBackendMetadata(String path) {
this(path, PropertyTransformers.noop());
}
private KeyValueSecretBackendMetadata(String path,
PropertyTransformer propertyTransformer) {
Assert.hasText(path, "Secret backend path must not be empty");
Assert.notNull(propertyTransformer, "PropertyTransformer must not be null");
this.path = path;
this.propertyTransformer = propertyTransformer;
}
/**
* Create a {@link SecretBackendMetadata} for the {@code kv} secret backend given a
* {@code secretBackendPath} and {@code key}. Use plain mount and key paths. The
* required {@code data} segment is added by this method.
*
* @param secretBackendPath the secret backend mount path without leading/trailing
* slashes and without the {@code data} path segment, must not be empty or
* {@literal null}.
* @param key the key within the secret backend. May contain slashes but not
* leading/trailing slashes, must not be empty or {@literal null}.
* @return the {@link SecretBackendMetadata}
*/
public static SecretBackendMetadata create(String secretBackendPath, String key) {
Assert.hasText(secretBackendPath, "Secret backend path must not be null or empty");
Assert.hasText(key, "Key must not be null or empty");
return create(String.format("%s/data/%s", secretBackendPath, key),
UnwrappingPropertyTransformer.unwrap("data"));
}
/**
* Create a {@link SecretBackendMetadata} for the {@code generic} secret backend given
* a {@code path}.
*
* @param path the relative path of the secret. slashes, must not be empty or
* {@literal null}.
* @return the {@link SecretBackendMetadata}
*/
public static SecretBackendMetadata create(String path) {
return new KeyValueSecretBackendMetadata(path, PropertyTransformers.noop());
}
/**
* Create a {@link SecretBackendMetadata} for the {@code generic} secret backend given
* a {@code path}.
*
* @param path the relative path of the secret. slashes, must not be empty or
* {@literal null}.
* @param propertyTransformer property transformer.
* @return the {@link SecretBackendMetadata}
*/
public static SecretBackendMetadata create(String path,
PropertyTransformer propertyTransformer) {
return new KeyValueSecretBackendMetadata(path, propertyTransformer);
}
@Override
public String getPath() {
return path;
}
@Override
public PropertyTransformer getPropertyTransformer() {
return propertyTransformer;
}
/**
* Build a list of context paths from application name and the active profile names.
* Application name and profiles support multiple (comma-separated) values.
*
* @param properties
* @param profiles active application profiles.
* @return list of context paths.
*/
public static List<String> buildContexts(
VaultKeyValueBackendPropertiesSupport properties, List<String> profiles) {
String appName = properties.getApplicationName();
Set<String> contexts = new LinkedHashSet<>();
String defaultContext = properties.getDefaultContext();
contexts.addAll(buildContexts(defaultContext, profiles,
properties.getProfileSeparator()));
for (String applicationName : StringUtils.commaDelimitedListToSet(appName)) {
contexts.addAll(buildContexts(applicationName, profiles,
properties.getProfileSeparator()));
}
List<String> result = new ArrayList<>(contexts);
Collections.reverse(result);
return result;
}
/**
* Create a list of context names from a combination of application name and
* application name with profile name. Using an empty application name will return an
* empty list.
*
* @param applicationName the application name. May be empty.
* @param profiles active application profiles.
* @param profileSeparator profile separator character between application name and
* profile name.
* @return list of context names.
*/
public static List<String> buildContexts(String applicationName,
List<String> profiles, String profileSeparator) {
List<String> contexts = new ArrayList<>();
if (!StringUtils.hasText(applicationName)) {
return contexts;
}
if (!contexts.contains(applicationName)) {
contexts.add(applicationName);
}
for (String profile : profiles) {
if (!StringUtils.hasText(profile)) {
continue;
}
String contextName = applicationName + profileSeparator + profile.trim();
if (!contexts.contains(contextName)) {
contexts.add(contextName);
}
}
return contexts;
}
/**
* {@link PropertyTransformer} that strips a prefix from property names.
*/
static class UnwrappingPropertyTransformer implements PropertyTransformer {
private final String prefixToStrip;
private UnwrappingPropertyTransformer(String prefixToStrip) {
Assert.notNull(prefixToStrip, "Property name prefix must not be null");
this.prefixToStrip = prefixToStrip;
}
/**
* Create a new {@link PropertyTransformers.KeyPrefixPropertyTransformer} that
* adds a prefix to each key name.
* @param propertyNamePrefix the property name prefix to be added in front of each
* property name, must not be {@literal null}.
* @return a new {@link PropertyTransformers.KeyPrefixPropertyTransformer} that
* adds a prefix to each key name.
*/
public static PropertyTransformer unwrap(String propertyNamePrefix) {
return new UnwrappingPropertyTransformer(propertyNamePrefix);
}
@Override
public Map<String, Object> transformProperties(Map<String, ? extends Object> input) {
Map<String, Object> target = new LinkedHashMap<>(input.size(), 1);
for (Entry<String, ? extends Object> entry : input.entrySet()) {
if (entry.getKey().startsWith(prefixToStrip + ".")) {
target.put(entry.getKey().substring(prefixToStrip.length() + 1),
entry.getValue());
}
}
return target;
}
}
}

View File

@@ -37,8 +37,6 @@ import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.core.lease.SecretLeaseContainer;
import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.*;
/**
* {@link org.springframework.cloud.bootstrap.BootstrapConfiguration Auto-configuration}
* for Spring Vault's {@link PropertySourceLocator} support.
@@ -48,7 +46,8 @@ import static org.springframework.cloud.vault.config.GenericSecretBackendMetadat
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
@EnableConfigurationProperties(VaultGenericBackendProperties.class)
@EnableConfigurationProperties({ VaultGenericBackendProperties.class,
VaultKeyValueBackendProperties.class })
@Order(Ordered.LOWEST_PRECEDENCE - 10)
public class VaultBootstrapPropertySourceConfiguration implements InitializingBean {
@@ -77,13 +76,15 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
@Bean
public PropertySourceLocator vaultPropertySourceLocator(VaultOperations operations,
VaultProperties vaultProperties,
VaultGenericBackendProperties vaultGenericBackendProperties,
VaultKeyValueBackendProperties kvBackendProperties,
VaultGenericBackendProperties genericBackendProperties,
ObjectFactory<SecretLeaseContainer> secretLeaseContainerObjectFactory) {
VaultConfigTemplate vaultConfigTemplate = new VaultConfigTemplate(operations,
vaultProperties);
PropertySourceLocatorConfiguration propertySourceLocatorConfiguration = getPropertySourceConfiguration(vaultGenericBackendProperties);
PropertySourceLocatorConfiguration configuration = getPropertySourceConfiguration(Arrays
.asList(kvBackendProperties, genericBackendProperties));
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
@@ -96,15 +97,22 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
secretLeaseContainer.start();
return new LeasingVaultPropertySourceLocator(vaultProperties,
propertySourceLocatorConfiguration, secretLeaseContainer);
configuration,
secretLeaseContainer);
}
return new VaultPropertySourceLocator(vaultConfigTemplate, vaultProperties,
propertySourceLocatorConfiguration);
configuration);
}
/**
* Apply configuration through {@link VaultConfigurer}.
*
* @param keyValueBackends configured backend (key-value, generic secret backend).
* @return
*/
private PropertySourceLocatorConfiguration getPropertySourceConfiguration(
VaultGenericBackendProperties vaultGenericBackendProperties) {
List<VaultKeyValueBackendPropertiesSupport> keyValueBackends) {
Collection<VaultConfigurer> configurers = applicationContext.getBeansOfType(
VaultConfigurer.class).values();
@@ -124,32 +132,43 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
if (secretBackendConfigurer.isRegisterDefaultGenericSecretBackends()) {
if (vaultGenericBackendProperties.isEnabled()) {
for (VaultKeyValueBackendPropertiesSupport keyValueBackend : keyValueBackends) {
List<String> contexts = GenericSecretBackendMetadata.buildContexts(
vaultGenericBackendProperties, Arrays.asList(applicationContext
if (!keyValueBackend.isEnabled()) {
continue;
}
List<String> contexts = KeyValueSecretBackendMetadata.buildContexts(
keyValueBackend, Arrays.asList(applicationContext
.getEnvironment().getActiveProfiles()));
for (String context : contexts) {
secretBackendConfigurer.add(create(
vaultGenericBackendProperties.getBackend(), context));
if (keyValueBackend instanceof VaultKeyValueBackendProperties) {
for (String context : contexts) {
secretBackendConfigurer.add(KeyValueSecretBackendMetadata.create(
keyValueBackend.getBackend(), context));
}
}
else {
for (String context : contexts) {
secretBackendConfigurer.add(GenericSecretBackendMetadata.create(
keyValueBackend.getBackend(), context));
}
}
}
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
.createSecretBackendMetadata(vaultSecretBackendDescriptors, factories);
for (SecretBackendMetadata metadata : backendAccessors) {
secretBackendConfigurer.add(metadata);
}
backendAccessors.forEach(secretBackendConfigurer::add);
}
if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) {
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
.createSecretBackendMetadata(vaultSecretBackendDescriptors, factories);
for (SecretBackendMetadata metadata : backendAccessors) {
secretBackendConfigurer.add(metadata);
}
backendAccessors.forEach(secretBackendConfigurer::add);
}
return secretBackendConfigurer;

View File

@@ -33,7 +33,8 @@ import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("spring.cloud.vault.generic")
@Data
@Validated
public class VaultGenericBackendProperties implements EnvironmentAware {
public class VaultGenericBackendProperties implements EnvironmentAware,
VaultKeyValueBackendPropertiesSupport {
/**
* Enable the generic backend.

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2018 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
*
* http://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 javax.validation.constraints.NotEmpty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
/**
* Configuration properties for Vault using the generic backend.
*
* @author Mark Paluch
* @since 2.0
*/
@ConfigurationProperties("spring.cloud.vault.kv")
@Data
@Validated
public class VaultKeyValueBackendProperties implements EnvironmentAware,
VaultKeyValueBackendPropertiesSupport {
/**
* Enable the kev-value backend.
*/
private boolean enabled = false;
/**
* Name of the default backend.
*/
@NotEmpty
private String backend = "secret";
/**
* Name of the default context.
*/
private String defaultContext = "application";
/**
* Profile-separator to combine application name and profile.
*/
@NotEmpty
private String profileSeparator = "/";
/**
* Application name to be used for the context.
*/
private String applicationName = "application";
@Override
public void setEnvironment(Environment environment) {
String springCloudVaultAppName = environment
.getProperty("spring.cloud.vault.application-name");
if (StringUtils.hasText(springCloudVaultAppName)) {
this.applicationName = springCloudVaultAppName;
}
else {
String springAppName = environment.getProperty("spring.application.name");
if (StringUtils.hasText(springAppName)) {
this.applicationName = springAppName;
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2018 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
*
* http://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;
/**
* Interface declaring Key-Value configuration properties.
*
* @author Mark Paluch
* @since 2.0
*/
public interface VaultKeyValueBackendPropertiesSupport {
/**
* @return {@literal true} if this backend configuration is enabled; {@literal false}
* otherwise.
*/
boolean isEnabled();
/**
* @return mound path of the secret backend.
*/
String getBackend();
/**
* @return default context path. Can be empty.
*/
String getDefaultContext();
/**
* Profile separator character.
*/
String getProfileSeparator();
/**
* @return the application name to use.
*/
String getApplicationName();
}

View File

@@ -31,7 +31,7 @@ import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.create;
import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.*;
/**
* Abstract {@link PropertySourceLocator} to create {@link PropertySource}s based on
@@ -223,7 +223,7 @@ public abstract class VaultPropertySourceLocatorSupport implements PropertySourc
private static class GenericPropertySourceLocatorConfiguration
implements EnvironmentAware, PropertySourceLocatorConfiguration {
private final VaultGenericBackendProperties genericBackendProperties;
private final VaultKeyValueBackendPropertiesSupport genericBackendProperties;
private Environment environment;

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2018 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
*
* http://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 java.util.HashMap;
import java.util.Map;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.*;
/**
* Integration test using config infrastructure with token authentication.
*
* <p>
* In case this test should fail because of SSL make sure you run the test within the
* spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is
* referenced with {@code ../work/keystore.jks}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = VaultVersionedKvBackendConfigTests.TestApplication.class, properties = {
"spring.cloud.vault.host=foo", "spring.cloud.vault.port=80",
"spring.cloud.vault.uri=https://localhost:8200",
"spring.cloud.vault.generic.enabled=false", "spring.cloud.vault.kv.enabled=true",
"spring.cloud.vault.kv.backend=versioned",
"spring.cloud.vault.application-name=testVaultApp" })
public class VaultVersionedKvBackendConfigTests {
@BeforeClass
public static void beforeClass() {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
Map<String, Object> object = new HashMap<>();
object.put("vault.value", "foo");
object.put("nested", Collections.singletonMap("key", "value"));
vaultRule
.prepare()
.getVaultOperations()
.write("versioned/data/testVaultApp",
Collections.singletonMap("data", object));
}
@Value("${vault.value}")
String configValue;
@Autowired
Environment environment;
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
assertThat(configValue).isEqualTo("foo");
}
@Test
public void shouldContainProperty() {
assertThat(environment.containsProperty("vault.value")).isTrue();
assertThat(environment.getProperty("vault.value")).isEqualTo("foo");
assertThat(environment.containsProperty("nested.key")).isTrue();
assertThat(environment.getProperty("nested.key")).isEqualTo("value");
}
@Test
public void shouldContainVaultBeans() {
// Beans are registered in parent (bootstrap) context.
ApplicationContext parent = applicationContext.getParent();
assertThat(parent.getBeanNamesForType(VaultTemplate.class)).isNotEmpty();
assertThat(parent.getBeanNamesForType(LeasingVaultPropertySourceLocator.class))
.isNotEmpty();
}
@Test
public void shouldNotContainRestTemplateArtifacts() {
// Beans are registered in parent (bootstrap) context.
ApplicationContext parent = applicationContext.getParent();
assertThat(parent.getBeanNamesForType(RestTemplate.class)).isEmpty();
assertThat(parent.getBeanNamesForType(ClientHttpRequestFactory.class)).isEmpty();
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}