diff --git a/spring-cloud-config-dependencies/pom.xml b/spring-cloud-config-dependencies/pom.xml
index ad1403e8..d16c3a5c 100644
--- a/spring-cloud-config-dependencies/pom.xml
+++ b/spring-cloud-config-dependencies/pom.xml
@@ -16,6 +16,7 @@
Spring Cloud Config Dependencies
5.1.3.201810200350-r
+ 2.2.0.RELEASE
2.0.0.RELEASE
@@ -40,6 +41,11 @@
spring-cloud-config-monitor
${project.version}
+
+ org.springframework.vault
+ spring-vault-core
+ ${spring-vault.version}
+
org.springframework.credhub
spring-credhub-core
diff --git a/spring-cloud-config-server/.flattened-pom.xml b/spring-cloud-config-server/.flattened-pom.xml
index 57e2fb4a..44f8c5e3 100644
--- a/spring-cloud-config-server/.flattened-pom.xml
+++ b/spring-cloud-config-server/.flattened-pom.xml
@@ -158,6 +158,13 @@
+
+ org.springframework.vault
+ spring-vault-core
+ 2.2.0.RELEASE
+ compile
+ true
+
org.springframework.credhub
spring-credhub-core
diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml
index f9280150..62580b77 100644
--- a/spring-cloud-config-server/pom.xml
+++ b/spring-cloud-config-server/pom.xml
@@ -53,6 +53,11 @@
org.springframework.security
spring-security-rsa
+
+ org.springframework.vault
+ spring-vault-core
+ true
+
org.springframework.credhub
spring-credhub-core
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
index c6846e2d..29f61970 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/EnvironmentRepositoryConfiguration.java
@@ -30,6 +30,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -70,6 +71,8 @@ import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepo
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepositoryFactory;
+import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository;
+import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.support.GoogleCloudSourceSupport;
import org.springframework.cloud.config.server.support.TransportConfigCallbackFactory;
import org.springframework.context.annotation.Bean;
@@ -82,6 +85,7 @@ import org.springframework.core.env.Environment;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.vault.core.VaultTemplate;
/**
* @author Dave Syer
@@ -98,11 +102,11 @@ import org.springframework.jdbc.core.JdbcTemplate;
RedisEnvironmentProperties.class, AwsS3EnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class,
VaultConfiguration.class, VaultRepositoryConfiguration.class,
- CredhubConfiguration.class, CredhubRepositoryConfiguration.class,
- SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class,
- GitRepositoryConfiguration.class, RedisRepositoryConfiguration.class,
- GoogleCloudSourceConfiguration.class, AwsS3RepositoryConfiguration.class,
- DefaultRepositoryConfiguration.class })
+ SpringVaultRepositoryConfiguration.class, CredhubConfiguration.class,
+ CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class,
+ NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class,
+ RedisRepositoryConfiguration.class, GoogleCloudSourceConfiguration.class,
+ AwsS3RepositoryConfiguration.class, DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
@Bean
@@ -208,6 +212,8 @@ public class EnvironmentRepositoryConfiguration {
}
@Configuration(proxyBeanMethods = false)
+ @ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
+ @SuppressWarnings("deprecation")
static class VaultFactoryConfig {
@Bean
@@ -223,6 +229,8 @@ public class EnvironmentRepositoryConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
+ @ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
+ @SuppressWarnings("deprecation")
static class VaultHttpClientConfig {
@Bean
@@ -232,6 +240,20 @@ public class EnvironmentRepositoryConfiguration {
}
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(VaultTemplate.class)
+ static class SpringVaultFactoryConfig {
+
+ @Bean
+ public SpringVaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(
+ ObjectProvider request, EnvironmentWatch watch,
+ ConfigTokenProvider tokenProvider) {
+ return new SpringVaultEnvironmentRepositoryFactory(request, watch,
+ tokenProvider);
+ }
+
+ }
+
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JdbcTemplate.class)
static class JdbcFactoryConfig {
@@ -345,7 +367,9 @@ class SvnRepositoryConfiguration {
}
@Configuration(proxyBeanMethods = false)
+@ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
@Profile("vault")
+@SuppressWarnings("deprecation")
class VaultRepositoryConfiguration {
@Bean
@@ -357,6 +381,20 @@ class VaultRepositoryConfiguration {
}
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(VaultTemplate.class)
+@Profile("vault")
+class SpringVaultRepositoryConfiguration {
+
+ @Bean
+ public SpringVaultEnvironmentRepository vaultEnvironmentRepository(
+ SpringVaultEnvironmentRepositoryFactory factory,
+ VaultEnvironmentProperties environmentProperties) {
+ return factory.build(environmentProperties);
+ }
+
+}
+
@Configuration(proxyBeanMethods = false)
@Profile("credhub")
class CredhubRepositoryConfiguration {
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java
new file mode 100644
index 00000000..e57a0354
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/AbstractVaultEnvironmentRepository.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.environment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.NotEmpty;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.environment.PropertySource;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.util.StringUtils;
+
+import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER;
+
+/**
+ * @author Spencer Gibb
+ * @author Mark Paluch
+ * @author Haroun Pacquee
+ * @author Haytham Mohamed
+ * @author Scott Frederick
+ */
+public abstract class AbstractVaultEnvironmentRepository
+ implements EnvironmentRepository, Ordered {
+
+ // TODO: move to watchState:String on findOne?
+ protected final ObjectProvider request;
+
+ protected final EnvironmentWatch watch;
+
+ /**
+ * The key in vault shared by all applications. Defaults to application. Set to empty
+ * to disable.
+ */
+ protected String defaultKey;
+
+ /**
+ * Vault profile separator. Defaults to comma.
+ */
+ @NotEmpty
+ protected String profileSeparator;
+
+ protected int order;
+
+ public AbstractVaultEnvironmentRepository(ObjectProvider request,
+ EnvironmentWatch watch, VaultEnvironmentProperties properties) {
+ this.defaultKey = properties.getDefaultKey();
+ this.profileSeparator = properties.getProfileSeparator();
+ this.order = properties.getOrder();
+ this.request = request;
+ this.watch = watch;
+ }
+
+ @Override
+ public Environment findOne(String application, String profile, String label) {
+ String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
+ List scrubbedProfiles = scrubProfiles(profiles);
+
+ List keys = findKeys(application, scrubbedProfiles);
+
+ Environment environment = new Environment(application, profiles, label, null,
+ getWatchState());
+
+ for (String key : keys) {
+ // read raw 'data' key from vault
+ String data = read(key);
+ if (data != null) {
+ // data is in json format of which, yaml is a superset, so parse
+ final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
+ yaml.setResources(new ByteArrayResource(data.getBytes()));
+ Properties properties = yaml.getObject();
+
+ if (!properties.isEmpty()) {
+ environment.add(new PropertySource("vault:" + key, properties));
+ }
+ }
+ }
+
+ return environment;
+ }
+
+ protected abstract String read(String key);
+
+ private String getWatchState() {
+ HttpServletRequest servletRequest = this.request.getIfAvailable();
+ if (servletRequest != null) {
+ String state = servletRequest.getHeader(STATE_HEADER);
+ return this.watch.watch(state);
+ }
+ return null;
+ }
+
+ private List findKeys(String application, List profiles) {
+ List keys = new ArrayList<>();
+
+ if (StringUtils.hasText(this.defaultKey)
+ && !this.defaultKey.equals(application)) {
+ keys.add(this.defaultKey);
+ addProfiles(keys, this.defaultKey, profiles);
+ }
+
+ // application may have comma-separated list of names
+ String[] applications = StringUtils.commaDelimitedListToStringArray(application);
+ for (String app : applications) {
+ keys.add(app);
+ addProfiles(keys, app, profiles);
+ }
+
+ Collections.reverse(keys);
+ return keys;
+ }
+
+ private List scrubProfiles(String[] profiles) {
+ List scrubbedProfiles = new ArrayList<>(Arrays.asList(profiles));
+ scrubbedProfiles.remove("default");
+ return scrubbedProfiles;
+ }
+
+ private void addProfiles(List contexts, String baseContext,
+ List profiles) {
+ for (String profile : profiles) {
+ contexts.add(baseContext + this.profileSeparator + profile);
+ }
+ }
+
+ public void setDefaultKey(String defaultKey) {
+ this.defaultKey = defaultKey;
+ }
+
+ public void setProfileSeparator(String profileSeparator) {
+ this.profileSeparator = profileSeparator;
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java
index 08f0bc7b..43863cd8 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepository.java
@@ -16,39 +16,31 @@
package org.springframework.cloud.config.server.environment;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
-import org.springframework.cloud.config.environment.Environment;
-import org.springframework.cloud.config.environment.PropertySource;
-import org.springframework.core.Ordered;
-import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;
-import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER;
-
/**
* @author Spencer Gibb
* @author Mark Paluch
* @author Haroun Pacquee
* @author Haytham Mohamed
* @author Scott Frederick
+ * @deprecated Prefer
+ * {@link org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository}
+ * instead of this environment repository implementation. The alternative implementation
+ * supports additional features including more authentication options, support for several
+ * underlying HTTP client libraries, and better SSL configuration.
*/
@Validated
-public class VaultEnvironmentRepository implements EnvironmentRepository, Ordered {
+public class VaultEnvironmentRepository extends AbstractVaultEnvironmentRepository {
/**
* Vault token header name.
@@ -76,28 +68,11 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
@NotEmpty
private String backend;
- /**
- * The key in vault shared by all applications. Defaults to application. Set to empty
- * to disable.
- */
- private String defaultKey;
-
/** Vault Namespace header value. */
private String namespace;
- /** Vault profile separator. Defaults to comma. */
- @NotEmpty
- private String profileSeparator;
-
- private int order;
-
private VaultKvAccessStrategy accessStrategy;
- // TODO: move to watchState:String on findOne?
- private final ObjectProvider request;
-
- private final EnvironmentWatch watch;
-
private final ConfigTokenProvider tokenProvider;
public VaultEnvironmentRepository(ObjectProvider request,
@@ -110,15 +85,11 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
public VaultEnvironmentRepository(ObjectProvider request,
EnvironmentWatch watch, RestTemplate rest,
VaultEnvironmentProperties properties, ConfigTokenProvider tokenProvider) {
- this.request = request;
- this.watch = watch;
+ super(request, watch, properties);
this.tokenProvider = tokenProvider;
this.backend = properties.getBackend();
- this.defaultKey = properties.getDefaultKey();
this.host = properties.getHost();
- this.order = properties.getOrder();
this.port = properties.getPort();
- this.profileSeparator = properties.getProfileSeparator();
this.scheme = properties.getScheme();
this.namespace = properties.getNamespace();
@@ -133,78 +104,7 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
}
@Override
- public Environment findOne(String application, String profile, String label) {
- String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
- List scrubbedProfiles = scrubProfiles(profiles);
-
- List keys = findKeys(application, scrubbedProfiles);
-
- Environment environment = new Environment(application, profiles, label, null,
- getWatchState());
-
- for (String key : keys) {
- // read raw 'data' key from vault
- String data = read(key);
- if (data != null) {
- // data is in json format of which, yaml is a superset, so parse
- final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
- yaml.setResources(new ByteArrayResource(data.getBytes()));
- Properties properties = yaml.getObject();
-
- if (!properties.isEmpty()) {
- environment.add(new PropertySource("vault:" + key, properties));
- }
- }
- }
-
- return environment;
- }
-
- private String getWatchState() {
- HttpServletRequest servletRequest = this.request.getIfAvailable();
- if (servletRequest != null) {
- String state = servletRequest.getHeader(STATE_HEADER);
- return this.watch.watch(state);
- }
- return null;
- }
-
- private List findKeys(String application, List profiles) {
- List keys = new ArrayList<>();
-
- if (StringUtils.hasText(this.defaultKey)
- && !this.defaultKey.equals(application)) {
- keys.add(this.defaultKey);
- addProfiles(keys, this.defaultKey, profiles);
- }
-
- // application may have comma-separated list of names
- String[] applications = StringUtils.commaDelimitedListToStringArray(application);
- for (String app : applications) {
- keys.add(app);
- addProfiles(keys, app, profiles);
- }
-
- Collections.reverse(keys);
- return keys;
- }
-
- private List scrubProfiles(String[] profiles) {
- List scrubbedProfiles = new ArrayList<>(Arrays.asList(profiles));
- if (scrubbedProfiles.contains("default")) {
- scrubbedProfiles.remove("default");
- }
- return scrubbedProfiles;
- }
-
- private void addProfiles(List contexts, String baseContext,
- List profiles) {
- for (String profile : profiles) {
- contexts.add(baseContext + this.profileSeparator + profile);
- }
- }
-
- private String read(String key) {
+ protected String read(String key) {
HttpHeaders headers = new HttpHeaders();
headers.add(VAULT_TOKEN, getToken());
if (StringUtils.hasText(this.namespace)) {
@@ -239,25 +139,8 @@ public class VaultEnvironmentRepository implements EnvironmentRepository, Ordere
this.backend = backend;
}
- public void setDefaultKey(String defaultKey) {
- this.defaultKey = defaultKey;
- }
-
- public void setProfileSeparator(String profileSeparator) {
- this.profileSeparator = profileSeparator;
- }
-
public void setNamespace(String namespace) {
this.namespace = namespace;
}
- @Override
- public int getOrder() {
- return this.order;
- }
-
- public void setOrder(int order) {
- this.order = order;
- }
-
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java
index 235788a3..ac94e5dd 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentRepositoryFactory.java
@@ -26,6 +26,11 @@ import org.springframework.web.client.RestTemplate;
/**
* @author Dylan Roberts
* @author Scott Frederick
+ * @deprecated Prefer
+ * {@link org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository}
+ * instead of this environment repository implementation. The alternative implementation
+ * supports additional features including more authentication options, support for several
+ * underlying HTTP client libraries, and better SSL configuration.
*/
public class VaultEnvironmentRepositoryFactory implements
EnvironmentRepositoryFactory {
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepository.java
new file mode 100644
index 00000000..b6ec51c0
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepository.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.environment.vault;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.config.server.environment.AbstractVaultEnvironmentRepository;
+import org.springframework.cloud.config.server.environment.EnvironmentWatch;
+import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.vault.core.VaultKeyValueOperations;
+import org.springframework.vault.support.VaultResponse;
+
+/**
+ * @author Scott Frederick
+ */
+@Validated
+public class SpringVaultEnvironmentRepository extends AbstractVaultEnvironmentRepository {
+
+ private VaultKeyValueOperations keyValueTemplate;
+
+ private final ObjectMapper objectMapper;
+
+ public SpringVaultEnvironmentRepository(ObjectProvider request,
+ EnvironmentWatch watch, VaultEnvironmentProperties properties,
+ VaultKeyValueOperations keyValueTemplate) {
+ super(request, watch, properties);
+ this.keyValueTemplate = keyValueTemplate;
+ this.objectMapper = new ObjectMapper();
+ }
+
+ protected String read(String key) {
+ VaultResponse response = this.keyValueTemplate.get(key);
+ if (response != null) {
+ try {
+ return objectMapper.writeValueAsString(response.getData());
+ }
+ catch (JsonProcessingException e) {
+ throw new RuntimeException("Error creating Vault response", e);
+ }
+ }
+ return null;
+ }
+
+ VaultKeyValueOperations getKeyValueTemplate() {
+ return this.keyValueTemplate;
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java
new file mode 100644
index 00000000..4d49b997
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.environment.vault;
+
+import java.net.URI;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
+import org.springframework.cloud.config.server.environment.EnvironmentRepositoryFactory;
+import org.springframework.cloud.config.server.environment.EnvironmentWatch;
+import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
+import org.springframework.util.StringUtils;
+import org.springframework.vault.VaultException;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.authentication.SimpleSessionManager;
+import org.springframework.vault.client.RestTemplateBuilder;
+import org.springframework.vault.client.VaultClients;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.core.VaultKeyValueOperations;
+import org.springframework.vault.core.VaultKeyValueOperationsSupport;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * @author Dylan Roberts
+ * @author Scott Frederick
+ */
+public class SpringVaultEnvironmentRepositoryFactory implements
+ EnvironmentRepositoryFactory {
+
+ private final ObjectProvider request;
+
+ private final EnvironmentWatch watch;
+
+ private final ConfigTokenProvider tokenProvider;
+
+ public SpringVaultEnvironmentRepositoryFactory(
+ ObjectProvider request, EnvironmentWatch watch,
+ ConfigTokenProvider tokenProvider) {
+ this.request = request;
+ this.watch = watch;
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public SpringVaultEnvironmentRepository build(
+ VaultEnvironmentProperties vaultProperties) {
+ RestTemplateBuilder restTemplateBuilder = buildRestTemplateBuilder(
+ vaultProperties);
+
+ VaultTemplate vaultTemplate = buildVaultTemplate(restTemplateBuilder);
+
+ VaultKeyValueOperations accessStrategy = buildVaultAccessStrategy(vaultProperties,
+ vaultTemplate);
+
+ return new SpringVaultEnvironmentRepository(this.request, this.watch,
+ vaultProperties, accessStrategy);
+ }
+
+ private RestTemplateBuilder buildRestTemplateBuilder(
+ VaultEnvironmentProperties vaultProperties) {
+ URI baseUrl = UriComponentsBuilder.newInstance()
+ .scheme(vaultProperties.getScheme()).host(vaultProperties.getHost())
+ .port(vaultProperties.getPort()).build().toUri();
+
+ RestTemplateBuilder restTemplateBuilder = RestTemplateBuilder.builder()
+ .endpoint(VaultEndpoint.from(baseUrl));
+
+ if (vaultProperties.getNamespace() != null) {
+ restTemplateBuilder.customizers(
+ restTemplate -> restTemplate.getInterceptors().add(VaultClients
+ .createNamespaceInterceptor(vaultProperties.getNamespace())));
+ }
+
+ return restTemplateBuilder;
+ }
+
+ private VaultTemplate buildVaultTemplate(RestTemplateBuilder restTemplateBuilder) {
+ return new VaultTemplate(restTemplateBuilder, new SimpleSessionManager(
+ new ConfigTokenProviderAuthentication(tokenProvider)));
+ }
+
+ private VaultKeyValueOperations buildVaultAccessStrategy(
+ VaultEnvironmentProperties vaultProperties, VaultTemplate vaultTemplate) {
+ String backend = vaultProperties.getBackend();
+ int version = vaultProperties.getKvVersion();
+
+ switch (version) {
+ case 1:
+ return vaultTemplate.opsForKeyValue(backend,
+ VaultKeyValueOperationsSupport.KeyValueBackend.KV_1);
+ case 2:
+ return vaultTemplate.opsForKeyValue(backend,
+ VaultKeyValueOperationsSupport.KeyValueBackend.KV_2);
+ default:
+ throw new IllegalArgumentException(
+ "No support for given Vault k/v backend version " + version);
+ }
+ }
+
+ public static class ConfigTokenProviderAuthentication
+ implements ClientAuthentication {
+
+ private final ConfigTokenProvider tokenProvider;
+
+ public ConfigTokenProviderAuthentication(ConfigTokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public VaultToken login() throws VaultException {
+ String token = tokenProvider.getToken();
+ if (!StringUtils.hasLength(token)) {
+ throw new IllegalArgumentException(
+ "A Vault token must be supplied by a token provider");
+ }
+ return VaultToken.of(token);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java
new file mode 100644
index 00000000..b82eadcf
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.environment.vault;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
+import org.springframework.cloud.config.server.environment.EnvironmentWatch;
+import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
+import org.springframework.vault.core.VaultKeyValueOperations;
+import org.springframework.vault.core.VaultKeyValueOperationsSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Scott Frederick
+ */
+public class SpringVaultEnvironmentRepositoryFactoryTests {
+
+ @Test
+ public void buildForVersion1() {
+ VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
+
+ SpringVaultEnvironmentRepository environmentRepository = new SpringVaultEnvironmentRepositoryFactory(
+ mockHttpRequest(), new EnvironmentWatch.Default(), mockTokenProvider())
+ .build(properties);
+
+ VaultKeyValueOperations keyValueTemplate = environmentRepository
+ .getKeyValueTemplate();
+ assertThat(keyValueTemplate.getApiVersion())
+ .isEqualTo(VaultKeyValueOperationsSupport.KeyValueBackend.KV_1);
+ }
+
+ @Test
+ public void buildForVersion2() {
+ VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
+ properties.setKvVersion(2);
+
+ SpringVaultEnvironmentRepository environmentRepository = new SpringVaultEnvironmentRepositoryFactory(
+ mockHttpRequest(), new EnvironmentWatch.Default(), mockTokenProvider())
+ .build(properties);
+
+ VaultKeyValueOperations keyValueTemplate = environmentRepository
+ .getKeyValueTemplate();
+ assertThat(keyValueTemplate.getApiVersion())
+ .isEqualTo(VaultKeyValueOperationsSupport.KeyValueBackend.KV_2);
+ }
+
+ private ConfigTokenProvider mockTokenProvider() {
+ ConfigTokenProvider tokenProvider = mock(ConfigTokenProvider.class);
+ when(tokenProvider.getToken()).thenReturn("token");
+ return tokenProvider;
+ }
+
+ @SuppressWarnings("unchecked")
+ private ObjectProvider mockHttpRequest() {
+ ObjectProvider objectProvider = mock(ObjectProvider.class);
+ when(objectProvider.getIfAvailable()).thenReturn(null);
+ return objectProvider;
+ }
+
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java
new file mode 100644
index 00000000..43b47228
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryTests.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2018-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.environment.vault;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.server.environment.EnvironmentWatch;
+import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
+import org.springframework.vault.core.VaultKeyValueOperations;
+import org.springframework.vault.support.VaultResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Spencer Gibb
+ * @author Ryan Baxter
+ * @author Haroun Pacquee
+ * @author Mark Paluch
+ * @author Haytham Mohamed
+ * @author Scott Frederick
+ */
+@SuppressWarnings("rawtypes")
+public class SpringVaultEnvironmentRepositoryTests {
+
+ @Test
+ public void testFindOneNoDefaultKey() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar"));
+ when(keyValueTemplate.get("application"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(),
+ new VaultEnvironmentProperties(), keyValueTemplate);
+
+ Environment e = repo.findOne("myapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp");
+ assertThat(e.getPropertySources()).as(
+ "Properties for specified application and default application with key 'application' should be returned")
+ .hasSize(2);
+
+ assertThat(e.getPropertySources().get(0).getSource()).as(
+ "Properties for specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("foo", "bar"));
+ assertThat(e.getPropertySources().get(1).getSource()).as(
+ "Properties for default application with key 'application' should be returned in second position")
+ .containsOnly((Map.Entry) entry("def-foo", "def-bar"));
+ }
+
+ @Test
+ public void testBackendWithSlashes() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar"));
+ when(keyValueTemplate.get("application"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
+ properties.setBackend("foo/bar/secret");
+
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(), properties,
+ keyValueTemplate);
+
+ Environment e = repo.findOne("myapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp");
+ assertThat(e.getPropertySources()).as(
+ "Properties for specified application and default application with key 'application' should be returned")
+ .hasSize(2);
+
+ assertThat(e.getPropertySources().get(0).getSource()).as(
+ "Properties for specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("foo", "bar"));
+ assertThat(e.getPropertySources().get(1).getSource()).as(
+ "Properties for default application with key 'application' should be returned in second position")
+ .containsOnly((Map.Entry) entry("def-foo", "def-bar"));
+ }
+
+ @Test
+ public void testFindOneDefaultKeySetAndDifferentToApplication() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar"));
+ when(keyValueTemplate.get("mydefaultkey"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(),
+ new VaultEnvironmentProperties(), keyValueTemplate);
+ repo.setDefaultKey("mydefaultkey");
+
+ Environment e = repo.findOne("myapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp");
+ assertThat(e.getPropertySources()).as(
+ "Properties for specified application and default application with key 'mydefaultkey' should be returned")
+ .hasSize(2);
+
+ assertThat(e.getPropertySources().get(0).getSource()).as(
+ "Properties for specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("foo", "bar"));
+ assertThat(e.getPropertySources().get(1).getSource()).as(
+ "Properties for default application with key 'mydefaultkey' should be returned in second position")
+ .containsOnly((Map.Entry) entry("def-foo", "def-bar"));
+ }
+
+ @Test
+ public void testFindOneDefaultKeySetAndDifferentToMultipleApplications() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp"))
+ .thenReturn(withVaultResponse("myapp-foo", "myapp-bar"));
+ when(keyValueTemplate.get("yourapp"))
+ .thenReturn(withVaultResponse("yourapp-foo", "yourapp-bar"));
+ when(keyValueTemplate.get("mydefaultkey"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(),
+ new VaultEnvironmentProperties(), keyValueTemplate);
+ repo.setDefaultKey("mydefaultkey");
+
+ Environment e = repo.findOne("myapp,yourapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp,yourapp");
+ assertThat(e.getPropertySources()).as(
+ "Properties for specified applications and default application with key 'mydefaultkey' should be returned")
+ .hasSize(3);
+
+ assertThat(e.getPropertySources().get(0).getSource()).as(
+ "Properties for first specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("yourapp-foo", "yourapp-bar"));
+
+ assertThat(e.getPropertySources().get(1).getSource()).as(
+ "Properties for second specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("myapp-foo", "myapp-bar"));
+
+ assertThat(e.getPropertySources().get(2).getSource()).as(
+ "Properties for default application with key 'mydefaultkey' should be returned in second position")
+ .containsOnly((Map.Entry) entry("def-foo", "def-bar"));
+ }
+
+ @Test
+ public void testFindOneDefaultKeySetAndEqualToApplication() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar"));
+ when(keyValueTemplate.get("application"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(),
+ new VaultEnvironmentProperties(), keyValueTemplate);
+ repo.setDefaultKey("myapp");
+
+ Environment e = repo.findOne("myapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp");
+ assertThat(e.getPropertySources())
+ .as("Only properties for specified application should be returned")
+ .hasSize(1);
+
+ assertThat(e.getPropertySources().get(0).getSource())
+ .as("Properties should be returned for specified application")
+ .containsOnly((Map.Entry) entry("foo", "bar"));
+ }
+
+ @Test
+ public void testVaultVersioning() {
+ VaultKeyValueOperations keyValueTemplate = mock(VaultKeyValueOperations.class);
+ when(keyValueTemplate.get("myapp")).thenReturn(withVaultResponse("foo", "bar"));
+ when(keyValueTemplate.get("application"))
+ .thenReturn(withVaultResponse("def-foo", "def-bar"));
+
+ final VaultEnvironmentProperties vaultEnvironmentProperties = new VaultEnvironmentProperties();
+ vaultEnvironmentProperties.setKvVersion(2);
+ SpringVaultEnvironmentRepository repo = new SpringVaultEnvironmentRepository(
+ mockHttpRequest(), new EnvironmentWatch.Default(),
+ vaultEnvironmentProperties, keyValueTemplate);
+
+ Environment e = repo.findOne("myapp", null, null);
+ assertThat(e.getName()).as("Name should be the same as the application argument")
+ .isEqualTo("myapp");
+ assertThat(e.getPropertySources()).as(
+ "Properties for specified application and default application with key 'application' should be returned")
+ .hasSize(2);
+
+ assertThat(e.getPropertySources().get(0).getSource()).as(
+ "Properties for specified application should be returned in priority position")
+ .containsOnly((Map.Entry) entry("foo", "bar"));
+ }
+
+ private VaultResponse withVaultResponse(String key, Object value) {
+ Map responseData = new HashMap<>();
+ responseData.put(key, value);
+
+ VaultResponse response = new VaultResponse();
+ response.setData(responseData);
+
+ return response;
+ }
+
+ @SuppressWarnings("unchecked")
+ private ObjectProvider mockHttpRequest() {
+ ObjectProvider objectProvider = mock(ObjectProvider.class);
+ when(objectProvider.getIfAvailable()).thenReturn(null);
+ return objectProvider;
+ }
+
+}