Add possibility to disable generic backend #12

This commit is contained in:
Mark Paluch
2016-06-23 00:13:00 +02:00
parent 915df54325
commit 656c181178
22 changed files with 639 additions and 203 deletions

View File

@@ -75,9 +75,9 @@ The HTTP service has resources in the form:
----
/secret/{application}
/secret/{application},{profile}
/secret/{application}/{profile}
/secret/{defaultContext}
/secret/{defaultContext},{profile}
/secret/{defaultContext}/{profile}
----
where the "application" is injected as the `spring.application.name` in the

View File

@@ -56,10 +56,10 @@ backend is enabled which accesses secret config settings via JSON endpoints.
The HTTP service has resources in the form:
----
/secret/{application}/{profile}
/secret/{application}
/secret/{application},{profile}
/secret/{defaultContext}/{profile}
/secret/{defaultContext}
/secret/{defaultContext},{profile}
----
where the "application" is injected as the `spring.application.name` in the

View File

@@ -125,6 +125,83 @@ public class MyUserIdMechanism implements AppIdUserIdMechanism {
}
----
== Backends
[[vault-client-generic]]
=== Generic Backend
Spring Cloud Vault supports at the basic level the generic secret backend.
The generic secret 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 and so Spring Cloud Vault
allows using the Application name set in `spring.application.name`
and a default context name (`application`) in combination with active profiles.
----
/secret/{application}/{profile}
/secret/{application}
/secret/{defaultContext}/{profile}
/secret/{defaultContext}
----
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.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
generic:
enabled: true
default-context: application
----
See also: https://www.vaultproject.io/docs/secrets/generic/index.html[Vault Documentation: Using the generic secret backend]
[[vault-client-consul]]
=== Consul
Spring Cloud Vault allows to obtain credentials for Hashicorp Consil.
The integration can be enabled by setting `spring.cloud.vault.consul.enabled=true`
(default `false`). The obtained token is stored in `spring.cloud.consul.token`
so using Spring Cloud Consul can pick up the generated
credentials without further configuration. You can configure the property name
by setting `spring.cloud.vault.consul.token-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
consul:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/consul/index.html[Vault Documentation: Setting up Consul with Vault]
[[vault-client-rabbitmq]]
=== RabbitMQ
Spring Cloud Vault allows to obtain credentials for RabbitMQ.
The integration can be enabled by setting `spring.cloud.vault.rabbit.enabled=true`
(default `false`). Username and password are stored in `spring.rabbit.username`
and `spring.rabbit.password` so using Spring Boot will pick up the generated
credentials without further configuration. You can configure the property names
by setting `spring.cloud.vault.rabbit.username-property` and
`spring.cloud.vault.rabbit.password-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
rabbit:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/rabbit/index.html[Vault Documentation: Setting up RabbitMQ with Vault]
[[vault-client-database-backends]]
== Database backends
@@ -212,49 +289,6 @@ spring.cloud.vault:
See also: https://www.vaultproject.io/docs/secrets/postgresql/index.html[Vault Documentation: Setting up PostgreSQL with Vault]
[[vault-client-consul]]
== Consul
Spring Cloud Vault allows to obtain credentials for Hashicorp Consil.
The integration can be enabled by setting `spring.cloud.vault.consul.enabled=true`
(default `false`). The obtained token is stored in `spring.cloud.consul.token`
so using Spring Cloud Consul can pick up the generated
credentials without further configuration. You can configure the property name
by setting `spring.cloud.vault.consul.token-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
consul:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/consul/index.html[Vault Documentation: Setting up Consul with Vault]
[[vault-client-rabbitmq]]
=== RabbitMQ
Spring Cloud Vault allows to obtain credentials for RabbitMQ.
The integration can be enabled by setting `spring.cloud.vault.rabbit.enabled=true`
(default `false`). Username and password are stored in `spring.rabbit.username`
and `spring.rabbit.password` so using Spring Boot will pick up the generated
credentials without further configuration. You can configure the property names
by setting `spring.cloud.vault.rabbit.username-property` and
`spring.cloud.vault.rabbit.password-property`.
[source,yaml]
----
spring.cloud.vault:
enabled: true
...
rabbit:
enabled: true
----
See also: https://www.vaultproject.io/docs/secrets/rabbit/index.html[Vault Documentation: Setting up RabbitMQ with Vault]
[[vault-client-fail-fast]]
== Vault Client Fail Fast

View File

@@ -20,8 +20,8 @@ import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
@@ -81,6 +81,12 @@ public class VaultConfigConsulBootstrapConfiguration {
return variables;
}
@Override
public String getName() {
return String.format("%s with Role %s", properties.getBackend(),
properties.getRole());
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {

View File

@@ -19,6 +19,8 @@ import org.springframework.cloud.vault.VaultSecretBackend;
/**
* Configuration properties interface for database secrets.
*
* @author Mark Paluch
*/
public interface DatabaseSecretProperties extends VaultSecretBackend {

View File

@@ -20,8 +20,8 @@ import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
@@ -58,8 +58,8 @@ public class VaultConfigDatabaseBootstrapConfiguration {
@Override
public SecureBackendAccessor createSecureBackendAccessor(
DatabaseSecretProperties properties) {
return forDatabase(properties);
DatabaseSecretProperties configurationProperties) {
return forDatabase(configurationProperties);
}
@Override
@@ -92,6 +92,12 @@ public class VaultConfigDatabaseBootstrapConfiguration {
return variables;
}
@Override
public String getName() {
return String.format("%s with Role %s", properties.getBackend(),
properties.getRole());
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {

View File

@@ -20,8 +20,8 @@ import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.cloud.vault.VaultSecretBackend;
import org.springframework.cloud.vault.config.SecureBackendAccessorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
@@ -82,6 +82,12 @@ public class VaultConfigRabbitMqBootstrapConfiguration {
return variables;
}
@Override
public String getName() {
return String.format("%s with Role %s", properties.getBackend(),
properties.getRole());
}
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {

View File

@@ -20,7 +20,6 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.vault.SecureBackendAccessor;
import org.springframework.cloud.vault.VaultProperties;
import org.springframework.util.Assert;
/**
@@ -31,20 +30,6 @@ import org.springframework.util.Assert;
*/
class SecureBackendAccessors {
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
* @param vaultProperties must not be {@literal null}.
* @param key must not be {@literal null} and not empty.
* @return the {@link SecureBackendAccessor}
*/
public static SecureBackendAccessor generic(VaultProperties vaultProperties,
String key) {
Assert.notNull(vaultProperties, "VaultProperties must not be null");
return generic(vaultProperties.getBackend(), key);
}
/**
* Creates a {@link SecureBackendAccessor} for the {@code generic} secure backend.
*
@@ -68,6 +53,11 @@ class SecureBackendAccessors {
return variables;
}
@Override
public String getName() {
return String.format("%s/%s", secretBackendPath, key);
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
return input;

View File

@@ -47,15 +47,21 @@ public class VaultConfigBootstrapConfiguration implements ApplicationContextAwar
private Collection<VaultSecretBackend> vaultSecretBackends;
private Collection<SecureBackendAccessorFactory<? super VaultSecretBackend>> factories;
@Bean
public VaultGenericBackendProperties vaultGenericBackendProperties() {
return new VaultGenericBackendProperties();
}
@Bean
public VaultPropertySourceLocator vaultPropertySourceLocator(VaultClient vaultClient,
VaultProperties vaultProperties) {
VaultProperties vaultProperties,
VaultGenericBackendProperties vaultGenericBackendProperties) {
Collection<SecureBackendAccessor> backendAccessors = SecureBackendFactories
.createBackendAcessors(vaultSecretBackends, factories);
return new VaultPropertySourceLocator(vaultClient, vaultProperties,
backendAccessors);
vaultGenericBackendProperties, backendAccessors);
}
@Override

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2016 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 org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* Configuration properties for Vault using the generic backend.
*
* @author Mark Paluch
*/
@ConfigurationProperties("spring.cloud.vault.generic")
@Data
public class VaultGenericBackendProperties {
/**
* Enable the generic backend.
*/
private boolean enabled = true;
/**
* Name of the default backend.
*/
@NotEmpty
private String backend = "secret";
/**
* Name of the default context.
*/
@NotEmpty
private String defaultContext = "application";
/**
* Profile-separator to combine application name and profile.
*/
@NotEmpty
private String profileSeparator = "/";
/**
* Application name to be used for the context.
*/
@org.springframework.beans.factory.annotation.Value("${spring.cloud.vault.applicationName:${spring.application.name:application}}")
private String applicationName;
}

View File

@@ -13,13 +13,9 @@
* 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.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,68 +37,64 @@ import lombok.extern.apachecommons.CommonsLog;
* @author Mark Paluch
*/
@CommonsLog
public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
private final VaultProperties vaultProperties;
private final SecureBackendAccessor secureBackendAccessor;
private final Map<String, String> properties = new LinkedHashMap<>();
private final transient VaultState vaultState;
private String context;
private Map<String, String> properties = new LinkedHashMap<>();
/**
* Creates a new {@link VaultPropertySource}.
*
* @param vaultClient must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param state shared Vault state, must not be {@literal null}.
* @param secureBackendAccessor must not be {@literal null}.
*/
public VaultPropertySource(VaultClient vaultClient,
VaultProperties properties, VaultState state,
SecureBackendAccessor secureBackendAccessor) {
private transient VaultState vaultState;
super(secureBackendAccessor.getName(), vaultClient);
Assert.notNull(vaultClient, "VaultClient must not be null!");
Assert.notNull(properties, "VaultProperties must not be null!");
Assert.notNull(state, "VaultState must not be null!");
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be null!");
public VaultPropertySource(String context, VaultClient source,
VaultProperties properties, VaultState state) {
super(context, source);
this.context = context;
this.vaultProperties = properties;
this.vaultState = state;
this.secureBackendAccessor = secureBackendAccessor;
}
public void init(Collection<SecureBackendAccessor> externalBackendAccessors) {
/**
* Initialize property source and read properties from Vault.
*/
public void init() {
Assert.hasText(vaultProperties.getBackend(),
"No generic secret backend configured (spring.cloud.vault.backend)");
List<SecureBackendAccessor> accessors = getSecureBackendAccessors(
externalBackendAccessors);
for (SecureBackendAccessor accessor : accessors) {
try {
Map<String, String> values = this.source.read(accessor, obtainToken());
if (values != null) {
this.properties.putAll(values);
}
}
catch (Exception e) {
String message = String.format(
"Unable to read properties from vault for %s ",
accessor.variables());
if (vaultProperties.isFailFast()) {
if (e instanceof RuntimeException) {
throw e;
}
throw new IllegalStateException(message, e);
}
log.error(message, e);
try {
Map<String, String> values = this.source.read(this.secureBackendAccessor,
obtainToken());
if (values != null) {
this.properties.putAll(values);
}
}
}
catch (Exception e) {
private List<SecureBackendAccessor> getSecureBackendAccessors(
Collection<SecureBackendAccessor> externalBackendAccessors) {
String message = String.format(
"Unable to read properties from Vault using %s for %s ", getName(),
secureBackendAccessor.variables());
if (vaultProperties.isFailFast()) {
if (e instanceof RuntimeException) {
throw e;
}
List<SecureBackendAccessor> accessors = new ArrayList<>();
throw new IllegalStateException(message, e);
}
accessors.add(SecureBackendAccessors.generic(vaultProperties.getBackend(),
this.context));
accessors.addAll(externalBackendAccessors);
return accessors;
log.error(message, e);
}
}
private VaultToken obtainToken() {
@@ -113,7 +105,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
if (vaultProperties.getAuthentication() == AuthenticationMethod.TOKEN) {
Assert.hasText(vaultProperties.getToken(), "Token must not be empty");
Assert.hasText(vaultProperties.getToken(), "Vault Token must not be empty");
vaultState.setToken(VaultToken.of(vaultProperties.getToken()));
return vaultState.getToken();

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.vault.config;
import static org.springframework.cloud.vault.config.SecureBackendAccessors.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -39,12 +41,12 @@ import org.springframework.util.StringUtils;
* @author Spencer Gibb
* @author Mark Paluch
*/
public class VaultPropertySourceLocator implements PropertySourceLocator {
class VaultPropertySourceLocator implements PropertySourceLocator {
private VaultClient vaultClient;
private VaultProperties properties;
private final Collection<SecureBackendAccessor> backendAcessors;
private final VaultClient vaultClient;
private final VaultProperties properties;
private final VaultGenericBackendProperties genericBackendProperties;
private final Collection<SecureBackendAccessor> backendAccessors;
private transient final VaultState vaultState = new VaultState();
@@ -52,65 +54,118 @@ public class VaultPropertySourceLocator implements PropertySourceLocator {
* Creates a new {@link VaultPropertySourceLocator}.
* @param vaultClient must not be {@literal null}.
* @param properties must not be {@literal null}.
* @param genericBackendProperties must not be {@literal null}.
* @param backendAccessors must not be {@literal null}.
*/
public VaultPropertySourceLocator(VaultClient vaultClient, VaultProperties properties,
VaultGenericBackendProperties genericBackendProperties,
Collection<SecureBackendAccessor> backendAccessors) {
Assert.notNull(vaultClient, "VaultClient must not be null");
Assert.notNull(properties, "VaultProperties must not be null");
Assert.notNull(backendAccessors, "BackendAccessors must not be null");
Assert.notNull(genericBackendProperties,
"VaultGenericBackendProperties must not be null");
this.vaultClient = vaultClient;
this.properties = properties;
this.backendAcessors = backendAccessors;
this.backendAccessors = backendAccessors;
this.genericBackendProperties = genericBackendProperties;
}
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
List<String> contexts = new ArrayList<>();
CompositePropertySource propertySource = createCompositePropertySource(
(ConfigurableEnvironment) environment);
initialize(propertySource);
String defaultContext = this.properties.getDefaultContext();
contexts.add(defaultContext);
addProfiles(contexts, defaultContext, profiles);
String baseContext = appName;
contexts.add(baseContext);
addProfiles(contexts, baseContext, profiles);
Collections.reverse(contexts);
CompositePropertySource composite = new CompositePropertySource("vault");
for (String propertySourceContext : contexts) {
if(StringUtils.hasText(propertySourceContext)) {
VaultPropertySource propertySource = create(propertySourceContext);
propertySource.init(backendAcessors);
composite.addPropertySource(propertySource);
}
}
return composite;
return propertySource;
}
return null;
}
private VaultPropertySource create(String context) {
return new VaultPropertySource(context, this.vaultClient, this.properties,
this.vaultState);
private List<String> buildContexts(ConfigurableEnvironment env) {
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
List<String> contexts = new ArrayList<>();
String defaultContext = genericBackendProperties.getDefaultContext();
if (StringUtils.hasText(defaultContext)) {
contexts.add(defaultContext);
}
addProfiles(contexts, defaultContext, profiles);
if (StringUtils.hasText(appName)) {
if (!contexts.contains(appName)) {
contexts.add(appName);
}
addProfiles(contexts, appName, profiles);
}
Collections.reverse(contexts);
return contexts;
}
protected CompositePropertySource createCompositePropertySource(
ConfigurableEnvironment environment) {
CompositePropertySource propertySource = new CompositePropertySource("vault");
if (genericBackendProperties.isEnabled()) {
List<String> contexts = buildContexts(environment);
for (String propertySourceContext : contexts) {
if (StringUtils.hasText(propertySourceContext)) {
VaultPropertySource vaultPropertySource = createVaultPropertySource(
generic(genericBackendProperties.getBackend(),
propertySourceContext));
propertySource.addPropertySource(vaultPropertySource);
}
}
}
for (SecureBackendAccessor backendAccessor : backendAccessors) {
VaultPropertySource vaultPropertySource = createVaultPropertySource(
backendAccessor);
propertySource.addPropertySource(vaultPropertySource);
}
return propertySource;
}
protected void initialize(CompositePropertySource propertySource) {
for (PropertySource<?> source : propertySource.getPropertySources()) {
((VaultPropertySource) source).init();
}
}
private VaultPropertySource createVaultPropertySource(
SecureBackendAccessor accessor) {
return new VaultPropertySource(this.vaultClient, this.properties, this.vaultState,
accessor);
}
private void addProfiles(List<String> contexts, String baseContext,
List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
String context = baseContext
+ this.genericBackendProperties.getProfileSeparator() + profile;
if (!contexts.contains(context)) {
contexts.add(context);
}
}
}
}

View File

@@ -52,7 +52,7 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
public void shouldReturnSecretsCorrectly() throws Exception {
Map<String, String> secretProperties = vaultClient
.read(generic(vaultProperties, "app-name"), createToken());
.read(generic("secret", "app-name"), createToken());
assertThat(secretProperties).containsAllEntriesOf(createExpectedMap());
}
@@ -61,7 +61,7 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
public void shouldReturnNullIfNotFound() throws Exception {
Map<String, String> secretProperties = vaultClient
.read(generic(vaultProperties, "missing"), createToken());
.read(generic("secret", "missing"), createToken());
assertThat(secretProperties).isEmpty();
}
@@ -70,7 +70,7 @@ public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
public void shouldFailOnFailFast() throws Exception {
vaultProperties.setFailFast(true);
vaultClient.read(generic(vaultProperties, "missing"), createToken());
vaultClient.read(generic("secret", "missing"), createToken());
}
/**

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2016 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 static org.assertj.core.api.Assertions.*;
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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.VaultClient;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration test using config infrastructure with token authentication. 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)
@SpringApplicationConfiguration(classes = VaultConfigDisabledTests.TestApplication.class)
@IntegrationTest("spring.cloud.vault.enabled=false")
public class VaultConfigDisabledTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
vaultRule.prepare().writeSecret("testVaultApp",
Collections.singletonMap("vault.value", "foo"));
}
@Autowired
Environment environment;
@Autowired
ApplicationContext applicationContext;
@Test
public void shouldNotContainVaultProperties() {
assertThat(environment.containsProperty("vault.value")).isFalse();
}
@Test
public void shouldNotContainVaultBeans() {
// Beans are registered in parent (bootstrap) context.
ApplicationContext parent = applicationContext.getParent();
assertThat(parent.getBeanNamesForType(VaultClient.class)).isEmpty();
assertThat(parent.getBeanNamesForType(VaultPropertySourceLocator.class))
.isEmpty();
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2016 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 static org.assertj.core.api.Assertions.*;
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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration test using config infrastructure with token authentication. 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)
@SpringApplicationConfiguration(classes = VaultConfigGenericBackendDisabledTests.TestApplication.class)
@IntegrationTest("spring.cloud.vault.generic.enabled=false")
public class VaultConfigGenericBackendDisabledTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
vaultRule.prepare().writeSecret("testVaultApp",
Collections.singletonMap("vault.value", "foo"));
}
@Autowired
Environment environment;
@Test
public void shouldNotContainVaultProperties() {
assertThat(environment.containsProperty("vault.value")).isFalse();
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}

View File

@@ -22,17 +22,24 @@ 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.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.VaultClient;
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.web.client.RestTemplate;
/**
* Integration test using config infrastructure with token authentication. 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}.
* Integration test using config infrastructure with token authentication. 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
*/
@@ -53,12 +60,45 @@ public class VaultConfigTests {
@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");
}
@Test
public void shouldContainVaultBeans() {
// Beans are registered in parent (bootstrap) context.
ApplicationContext parent = applicationContext.getParent();
assertThat(parent.getBeanNamesForType(VaultClient.class)).isNotEmpty();
assertThat(parent.getBeanNamesForType(VaultPropertySourceLocator.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 {

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2016 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 static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.vault.util.VaultRule;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration test using config infrastructure with token authentication. 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)
@SpringApplicationConfiguration(classes = VaultConfigWithContextTests.TestApplication.class)
@ActiveProfiles("my-profile")
public class VaultConfigWithContextTests {
@BeforeClass
public static void beforeClass() throws Exception {
VaultRule vaultRule = new VaultRule();
vaultRule.before();
vaultRule.prepare().writeSecret("testVaultApp/my-profile",
Collections.singletonMap("vault.value", "hello"));
vaultRule.prepare().writeSecret("testVaultApp",
Collections.singletonMap("vault.value", "worls"));
}
@Value("${vault.value}")
String configValue;
@Test
public void contextLoads() {
assertThat(configValue).isEqualTo("hello");
}
@SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
}

View File

@@ -38,4 +38,9 @@ public interface SecureBackendAccessor {
*/
Map<String, String> transformProperties(Map<String, String> input);
/**
*
* @return the name for this accessor.
*/
String getName();
}

View File

@@ -48,18 +48,14 @@ public class VaultBootstrapConfiguration {
ClientHttpRequestFactoryFactory.create(vaultProperties()));
}
@Bean
@Qualifier("vault-RestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
}
@Bean
public VaultClient vaultClient(ApplicationContext applicationContext) {
RestTemplate restTemplate = new RestTemplate(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
VaultClient vaultClient = new VaultClient(vaultProperties());
vaultClient.setRest(restTemplate());
vaultClient.setRest(restTemplate);
Map<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext
.getBeansOfType(AppIdUserIdMechanism.class);

View File

@@ -28,7 +28,6 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
@@ -66,17 +65,25 @@ public class VaultClient {
this.properties = properties;
}
/**
* Read secrets using the given {@link SecureBackendAccessor} and {@link VaultToken}.
*
* @param secureBackendAccessor must not be {@literal null}.
* @param vaultToken must not be {@literal null}.
* @return A {@link Map} containing properties.
*/
public Map<String, String> read(SecureBackendAccessor secureBackendAccessor,
VaultToken vaultToken) {
Assert.notNull(secureBackendAccessor, "SecureBackendAccessor must not be empty!");
Assert.notNull(vaultToken, "VaultToken must not be null!");
Assert.notNull(vaultToken, "Vault Token must not be null!");
String url = buildUrl();
HttpHeaders headers = createHeaders(vaultToken);
Exception error = null;
String errorBody = null;
HttpStatus status = null;
URI uri = this.rest.getUriTemplateHandler().expand(url,
secureBackendAccessor.variables());
@@ -86,7 +93,7 @@ public class VaultClient {
ResponseEntity<VaultResponse> response = this.rest.exchange(uri,
HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class);
HttpStatus status = response.getStatusCode();
status = response.getStatusCode();
if (status == HttpStatus.OK) {
if (response.getBody().getData() != null) {
return secureBackendAccessor
@@ -101,29 +108,42 @@ public class VaultClient {
errorBody = e.getResponseBodyAsString();
}
if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
error = e;
}
status = e.getStatusCode();
error = e;
}
catch (Exception e) {
error = e;
}
if (status == HttpStatus.NOT_FOUND) {
log.info(String.format("Could not locate PropertySource: %s",
"key not found"));
}
else if (status != null) {
log.warn(String.format("Could not locate PropertySource: Status %d %s",
status.value(),
getErrorMessage(error, errorBody)));
}
else {
log.warn(String.format("Could not locate PropertySource: %s",
(getErrorMessage(error, errorBody))));
}
if (properties.isFailFast()) {
throw new IllegalStateException(
"Could not locate PropertySource and the fail fast property is set, failing",
error);
}
log.warn(
String.format("Could not locate PropertySource: %s",
(errorBody == null
? error == null ? "key not found" : error.getMessage()
: errorBody)));
return Collections.emptyMap();
}
protected String getErrorMessage(Exception error, String errorBody) {
return errorBody == null
? error == null ? "unknown reason" : error.getMessage()
: errorBody;
}
private HttpHeaders createHeaders(VaultToken vaultToken) {
HttpHeaders headers = new HttpHeaders();

View File

@@ -52,24 +52,6 @@ public class VaultProperties {
*/
private String scheme = "https";
/**
* Name of the default backend.
*/
@NotEmpty
private String backend = "secret";
/**
* Name of the default context.
*/
@NotEmpty
private String defaultContext = "application";
/**
* Profile-separator to combine application name and profile.
*/
@NotEmpty
private String profileSeparator = ",";
/**
* Connection timeout;
*/

View File

@@ -13,7 +13,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-starter-config</artifactId>
<name>Spring Cloud Vault ConfigStarter</name>
<name>Spring Cloud Vault Config Starter</name>
<description>Starter for exposing Spring Cloud Vault Config</description>
<url>http://projects.spring.io/spring-boot/</url>