Add X-Vault-Namespace Support.

We now support Vault namespaces by setting the spring.cloud.vault.namespace property.

Closes gh-259
This commit is contained in:
Mark Paluch
2019-09-09 15:21:58 +02:00
parent 7e22219e09
commit fb00ff2672
4 changed files with 106 additions and 25 deletions

View File

@@ -1291,6 +1291,28 @@ spring.cloud.vault:
----
====
[[vault.config.namespaces]]
== Vault Enterprise Namespace Support
Vault Enterprise allows using namespaces to isolate multiple Vaults
on a single Vault server. Configuring a namespace by setting
`spring.cloud.vault.namespace=…` enables the namespace header
`X-Vault-Namespace` on every outgoing HTTP request when using the Vault
`RestTemplate` or `WebClient`.
Please note that this feature is not supported by Vault Community edition
and has no effect on Vault operations.
====
[source,yaml]
----
spring.cloud.vault:
namespace: my-namespace
----
====
See also: https://www.vaultproject.io/docs/enterprise/namespaces/index.html[Vault Enterprise: Namespaces]
[[vault.config.ssl]]
== Vault Client SSL configuration

View File

@@ -17,6 +17,9 @@
package org.springframework.cloud.vault.config;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@@ -31,20 +34,25 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.StringUtils;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.RestTemplateCustomizer;
import org.springframework.vault.client.RestTemplateRequestCustomizer;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.ClientOptions;
@@ -70,10 +78,14 @@ public class VaultBootstrapConfiguration implements InitializingBean {
private final VaultEndpointProvider endpointProvider;
private final List<RestTemplateCustomizer> customizers;
private final List<RestTemplateRequestCustomizer<?>> requestCustomizers;
/**
* Used for Vault communication.
*/
private RestOperations restOperations;
private RestTemplateBuilder restTemplateBuilder;
/**
* Used for external (AWS, GCP) communication.
@@ -82,7 +94,9 @@ public class VaultBootstrapConfiguration implements InitializingBean {
public VaultBootstrapConfiguration(ConfigurableApplicationContext applicationContext,
VaultProperties vaultProperties,
ObjectProvider<VaultEndpointProvider> endpointProvider) {
ObjectProvider<VaultEndpointProvider> endpointProvider,
ObjectProvider<List<RestTemplateCustomizer>> customizers,
ObjectProvider<List<RestTemplateRequestCustomizer<?>>> requestCustomizers) {
this.applicationContext = applicationContext;
this.vaultProperties = vaultProperties;
@@ -95,6 +109,13 @@ public class VaultBootstrapConfiguration implements InitializingBean {
}
this.endpointProvider = provider;
this.customizers = new ArrayList<>(
customizers.getIfAvailable(Collections::emptyList));
AnnotationAwareOrderComparator.sort(this.customizers);
this.requestCustomizers = new ArrayList<>(
requestCustomizers.getIfAvailable(Collections::emptyList));
AnnotationAwareOrderComparator.sort(this.requestCustomizers);
}
@Override
@@ -103,8 +124,17 @@ public class VaultBootstrapConfiguration implements InitializingBean {
ClientHttpRequestFactory clientHttpRequestFactory = clientHttpRequestFactoryWrapper()
.getClientHttpRequestFactory();
this.restOperations = VaultClients.createRestTemplate(this.endpointProvider,
clientHttpRequestFactory);
this.restTemplateBuilder = RestTemplateBuilder.builder()
.requestFactory(clientHttpRequestFactory)
.endpointProvider(this.endpointProvider);
this.customizers.forEach(this.restTemplateBuilder::customizers);
this.requestCustomizers.forEach(this.restTemplateBuilder::requestCustomizers);
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
this.restTemplateBuilder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE,
this.vaultProperties.getNamespace());
}
this.externalRestOperations = new RestTemplate(clientHttpRequestFactory);
}
@@ -142,9 +172,7 @@ public class VaultBootstrapConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean(VaultOperations.class)
public VaultTemplate vaultTemplate(SessionManager sessionManager) {
return new VaultTemplate(this.endpointProvider,
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(),
sessionManager);
return new VaultTemplate(this.restTemplateBuilder, sessionManager);
}
/**
@@ -184,9 +212,10 @@ public class VaultBootstrapConfiguration implements InitializingBean {
ObjectFactory<TaskSchedulerWrapper> asyncTaskExecutorFactory) {
if (this.vaultProperties.getConfig().getLifecycle().isEnabled()) {
RestTemplate restTemplate = this.restTemplateBuilder.build();
return new LifecycleAwareSessionManager(clientAuthentication,
asyncTaskExecutorFactory.getObject().getTaskScheduler(),
this.restOperations);
restTemplate);
}
return new SimpleSessionManager(clientAuthentication);
@@ -202,8 +231,9 @@ public class VaultBootstrapConfiguration implements InitializingBean {
@ConditionalOnMissingBean
public ClientAuthentication clientAuthentication() {
RestTemplate restTemplate = this.restTemplateBuilder.build();
ClientAuthenticationFactory factory = new ClientAuthenticationFactory(
this.vaultProperties, this.restOperations, this.externalRestOperations);
this.vaultProperties, restTemplate, this.externalRestOperations);
return factory.createClientAuthentication();
}

View File

@@ -70,6 +70,11 @@ public class VaultProperties implements EnvironmentAware {
*/
private String uri;
/**
* Vault namespace (requires Vault Enterprise).
*/
private String namespace;
/**
* Discovery properties.
*/
@@ -154,6 +159,10 @@ public class VaultProperties implements EnvironmentAware {
return this.uri;
}
public String getNamespace() {
return this.namespace;
}
public Discovery getDiscovery() {
return this.discovery;
}
@@ -246,6 +255,10 @@ public class VaultProperties implements EnvironmentAware {
this.uri = uri;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setDiscovery(Discovery discovery) {
this.discovery = discovery;
}

View File

@@ -17,6 +17,9 @@
package org.springframework.cloud.vault.config;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -36,10 +39,12 @@ import org.springframework.cloud.vault.config.VaultBootstrapConfiguration.TaskSc
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.AuthenticationStepsOperator;
import org.springframework.vault.authentication.CachingVaultTokenSupplier;
@@ -49,10 +54,12 @@ import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.ReactiveVaultClients;
import org.springframework.vault.client.ClientHttpConnectorFactory;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.config.ClientHttpConnectorFactory;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.client.WebClientCustomizer;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.support.ClientOptions;
@@ -80,12 +87,14 @@ public class VaultReactiveBootstrapConfiguration {
private final VaultProperties vaultProperties;
private final VaultEndpointProvider endpointProvider;
private final ClientHttpConnector clientHttpConnector;
/**
* Used for Vault communication.
*/
private final WebClientBuilder webClientBuilder;
public VaultReactiveBootstrapConfiguration(VaultProperties vaultProperties,
ObjectProvider<VaultEndpointProvider> endpointProvider) {
ObjectProvider<VaultEndpointProvider> endpointProvider,
ObjectProvider<List<WebClientCustomizer>> webClientCustomizers) {
this.vaultProperties = vaultProperties;
@@ -96,9 +105,19 @@ public class VaultReactiveBootstrapConfiguration {
.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties));
}
this.endpointProvider = provider;
this.webClientBuilder = WebClientBuilder.builder()
.httpConnector(createConnector(this.vaultProperties))
.endpointProvider(provider);
List<WebClientCustomizer> customizers = new ArrayList<>(
webClientCustomizers.getIfAvailable(Collections::emptyList));
AnnotationAwareOrderComparator.sort(customizers);
this.clientHttpConnector = createConnector(this.vaultProperties);
customizers.forEach(this.webClientBuilder::customizers);
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
this.webClientBuilder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE,
this.vaultProperties.getNamespace());
}
}
/**
@@ -130,8 +149,7 @@ public class VaultReactiveBootstrapConfiguration {
@ConditionalOnMissingBean(ReactiveVaultOperations.class)
public ReactiveVaultTemplate reactiveVaultTemplate(
ReactiveSessionManager tokenSupplier) {
return new ReactiveVaultTemplate(this.endpointProvider, this.clientHttpConnector,
tokenSupplier);
return new ReactiveVaultTemplate(this.webClientBuilder, tokenSupplier);
}
/**
@@ -152,8 +170,7 @@ public class VaultReactiveBootstrapConfiguration {
if (this.vaultProperties.getConfig().getLifecycle().isEnabled()) {
WebClient webClient = ReactiveVaultClients
.createWebClient(this.endpointProvider, this.clientHttpConnector);
WebClient webClient = this.webClientBuilder.build();
return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier,
asyncTaskExecutorFactory.getObject().getTaskScheduler(), webClient);
}
@@ -225,8 +242,7 @@ public class VaultReactiveBootstrapConfiguration {
private VaultTokenSupplier createAuthenticationStepsOperator(
AuthenticationStepsFactory factory) {
WebClient webClient = ReactiveVaultClients.createWebClient(this.endpointProvider,
this.clientHttpConnector);
WebClient webClient = this.webClientBuilder.build();
return new AuthenticationStepsOperator(factory.getAuthenticationSteps(),
webClient);
}