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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user