Extract bean instance creation into VaultConfiguration and VaultReactiveAutoConfiguration

This commit is contained in:
Mark Paluch
2020-09-18 15:53:32 +02:00
parent b86510a852
commit c71bb083e6
21 changed files with 1292 additions and 338 deletions

View File

@@ -34,6 +34,7 @@ import org.springframework.vault.core.lease.event.LeaseErrorListener;
* @author Mark Paluch
* @see LeaseAwareVaultPropertySource
*/
@Deprecated
class LeasingVaultPropertySourceLocator extends VaultPropertySourceLocatorSupport implements PriorityOrdered {
private final SecretLeaseContainer secretLeaseContainer;

View File

@@ -47,7 +47,7 @@ class PropertySourceLocatorConfigurationFactory {
* @return the {@link PropertySourceLocatorConfiguration}.
*/
PropertySourceLocatorConfiguration getPropertySourceConfiguration(
List<VaultKeyValueBackendPropertiesSupport> keyValueBackends) {
VaultKeyValueBackendPropertiesSupport... keyValueBackends) {
DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer();

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.vault.config;
import org.springframework.cloud.bootstrap.BootstrapConfiguration;
import org.springframework.context.ApplicationContext;
/**
* Strategy interface to create {@link SecretBackendMetadata} from
* {@link VaultSecretBackendDescriptor} properties. Mainly for internal use within the
@@ -31,7 +34,10 @@ package org.springframework.cloud.vault.config;
*
* <p>
* Typically implemented by secret backend providers that implement access to a particular
* backend using read operations.
* backend using read operations. Objects implementing this interface can be discovered
* either from the {@link ApplicationContext} when using {@link BootstrapConfiguration}
* (deprecated since 3.0) or {@code spring.factories} when using
* {@link ConfigDataLocationResolver}.
*
* @param <T> descriptor type.
* @author Mark Paluch

View File

@@ -17,7 +17,6 @@
package org.springframework.cloud.vault.config;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
@@ -45,19 +44,25 @@ import static org.springframework.cloud.vault.config.VaultAutoConfiguration.Task
* @author Grenville Wilson
* @author Mårten Svantesson
* @since 1.1
* @deprecated since 3.0, use {@code spring.config.import=vault://} instead.
*/
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
@EnableConfigurationProperties(VaultKeyValueBackendProperties.class)
@Order(Ordered.LOWEST_PRECEDENCE - 10)
@Deprecated
public class VaultBootstrapPropertySourceConfiguration implements InitializingBean {
private final VaultConfiguration configuration;
private final ConfigurableApplicationContext applicationContext;
private Collection<VaultSecretBackendDescriptor> vaultSecretBackendDescriptors;
private Collection<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> factories;
public VaultBootstrapPropertySourceConfiguration(ConfigurableApplicationContext applicationContext) {
public VaultBootstrapPropertySourceConfiguration(VaultProperties vaultProperties,
ConfigurableApplicationContext applicationContext) {
this.configuration = new VaultConfiguration(vaultProperties);
this.applicationContext = applicationContext;
}
@@ -84,8 +89,7 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
PropertySourceLocatorConfigurationFactory factory = new PropertySourceLocatorConfigurationFactory(
vaultConfigurers, this.vaultSecretBackendDescriptors, this.factories);
PropertySourceLocatorConfiguration configuration = factory
.getPropertySourceConfiguration(Collections.singletonList(kvBackendProperties));
PropertySourceLocatorConfiguration configuration = factory.getPropertySourceConfiguration(kvBackendProperties);
VaultProperties.ConfigLifecycle lifecycle = vaultProperties.getConfig().getLifecycle();
@@ -106,7 +110,6 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
}
/**
* @param vaultProperties the {@link VaultProperties}.
* @param vaultOperations the {@link VaultOperations}.
* @param taskSchedulerWrapper the {@link TaskSchedulerWrapper}.
* @return the {@link SessionManager} for Vault session management.
@@ -116,35 +119,9 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
@Bean
@Lazy
@ConditionalOnMissingBean
public SecretLeaseContainer secretLeaseContainer(VaultProperties vaultProperties, VaultOperations vaultOperations,
public SecretLeaseContainer secretLeaseContainer(VaultOperations vaultOperations,
TaskSchedulerWrapper taskSchedulerWrapper) {
VaultProperties.ConfigLifecycle lifecycle = vaultProperties.getConfig().getLifecycle();
SecretLeaseContainer container = new SecretLeaseContainer(vaultOperations,
taskSchedulerWrapper.getTaskScheduler());
customizeContainer(lifecycle, container);
return container;
}
static void customizeContainer(VaultProperties.ConfigLifecycle lifecycle, SecretLeaseContainer container) {
if (lifecycle.isEnabled()) {
if (lifecycle.getMinRenewal() != null) {
container.setMinRenewal(lifecycle.getMinRenewal());
}
if (lifecycle.getExpiryThreshold() != null) {
container.setExpiryThreshold(lifecycle.getExpiryThreshold());
}
if (lifecycle.getLeaseEndpoints() != null) {
container.setLeaseEndpoints(lifecycle.getLeaseEndpoints());
}
}
return this.configuration.createSecretLeaseContainer(vaultOperations, taskSchedulerWrapper::getTaskScheduler);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019-2020 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.vault.config;
import org.springframework.boot.Bootstrapper;
import org.springframework.util.Assert;
/**
* Utility to customize Bootstrapping of Vault when using the ConfigData API when
* importing {@code vault://}.
*
* @author Mark Paluch
* @since 3.0
*/
public abstract class VaultBootstrapper {
private VaultBootstrapper() {
}
/**
* Create a {@link Bootstrapper} that configures a {@link VaultConfigurer}.
* @param configurer the configurer to apply.
* @return the bootstrapper object.
*/
public static Bootstrapper fromConfigurer(VaultConfigurer configurer) {
Assert.notNull(configurer, "VaultConfigurer must not be null");
return registry -> registry.register(VaultConfigurer.class, context -> configurer);
}
}

View File

@@ -19,31 +19,41 @@ package org.springframework.cloud.vault.config;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.Bootstrapper;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataLoader;
import org.springframework.boot.context.config.ConfigDataLoaderContext;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.cloud.vault.config.VaultAutoConfiguration.TaskSchedulerWrapper;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.PropertySource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport;
import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.RestTemplateFactory;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.core.env.LeaseAwareVaultPropertySource;
import org.springframework.vault.core.lease.SecretLeaseContainer;
@@ -54,113 +64,64 @@ import org.springframework.web.client.RestTemplate;
import static org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
/**
* {@link ConfigDataLoader} for Vault for {@link VaultConfigLocation}.
* {@link ConfigDataLoader} for Vault for {@link VaultConfigLocation}. This class
* materializes {@link PropertySource property sources} by using Vault and
* {@link VaultConfigLocation}. This class also ensures that all necessary infrastructure
* beans are registered in the {@link BootstrapRegistry}. Registrations made by this
* config data loader are typically propagated into the {@link BeanFactory} as this
* configuration mirrors to some extent {@link VaultAutoConfiguration} and
* {@link VaultReactiveAutoConfiguration}.
* <p>
* Infrastructure beans can be customized by registering instances through
* {@link Bootstrapper}.
*
* @author Mark Paluch
* @since 3.0
* @see VaultConfigLocation
* @see VaultAutoConfiguration
* @see VaultReactiveAutoConfiguration
*/
public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocation> {
private final static boolean FLUX_AVAILABLE = ClassUtils.isPresent("reactor.core.publisher.Flux",
VaultConfigDataLoader.class.getClassLoader());
private final static boolean WEBCLIENT_AVAILABLE = ClassUtils.isPresent(
"org.springframework.web.reactive.function.client.WebClient", VaultConfigDataLoader.class.getClassLoader());
private final static boolean REGISTER_REACTIVE_INFRASTRUCTURE = FLUX_AVAILABLE && WEBCLIENT_AVAILABLE;
@Override
public ConfigData load(ConfigDataLoaderContext context, VaultConfigLocation location)
throws IOException, ConfigDataLocationNotFoundException {
ConfigurableBootstrapContext bootstrap = context.getBootstrapContext();
VaultProperties vaultProperties = bootstrap.get(VaultProperties.class);
ImperativeConfiguration configuration = new ImperativeConfiguration(vaultProperties);
if (vaultProperties.getSession().getLifecycle().isEnabled()
|| vaultProperties.getConfig().getLifecycle().isEnabled()) {
bootstrap.registerIfAbsent(TaskSchedulerWrapper.class, ctx -> {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(2);
threadPoolTaskScheduler.setDaemon(true);
threadPoolTaskScheduler.setThreadNamePrefix("Spring-Cloud-Vault-");
threadPoolTaskScheduler.afterPropertiesSet();
// TODO
// This is to destroy bootstrap resources
// otherwise, the bootstrap context is not shut down cleanly
// this.applicationContext.registerShutdownHook();
return new TaskSchedulerWrapper(threadPoolTaskScheduler);
});
registerVaultTaskScheduler(bootstrap);
}
bootstrap.registerIfAbsent(ClientFactoryWrapper.class, ctx -> new ClientFactoryWrapper(
VaultConfigurationUtil.createClientHttpRequestFactory(vaultProperties)));
bootstrap.registerIfAbsent(RestTemplateBuilder.class, ctx -> configuration
.createRestTemplateBuilder(ctx.get(ClientFactoryWrapper.class).getClientHttpRequestFactory()));
bootstrap.registerIfAbsent(RestTemplateFactory.class,
ctx -> new DefaultRestTemplateFactory(ctx.get(ClientFactoryWrapper.class).getClientHttpRequestFactory(),
configuration::createRestTemplateBuilder));
registerImperativeInfrastructure(bootstrap, vaultProperties);
ClientHttpRequestFactory factory = bootstrap.get(ClientFactoryWrapper.class).getClientHttpRequestFactory();
RestTemplate externalRestTemplate = new RestTemplate(factory);
ClientAuthenticationFactory authenticationFactory = new ClientAuthenticationFactory(vaultProperties,
bootstrap.get(RestTemplateFactory.class).create(), externalRestTemplate);
ClientAuthentication clientAuthentication = authenticationFactory.createClientAuthentication();
VaultProperties.AuthenticationMethod authentication = vaultProperties.getAuthentication();
if (authentication == VaultProperties.AuthenticationMethod.NONE) {
bootstrap.registerIfAbsent(VaultTemplate.class,
ctx -> new VaultTemplate(ctx.get(RestTemplateBuilder.class)));
if (REGISTER_REACTIVE_INFRASTRUCTURE) {
registerReactiveInfrastructure(bootstrap, vaultProperties);
}
else {
bootstrap.registerIfAbsent(SessionManager.class, ctx -> {
// TODO: Create blocking adapter for Reactive Session Manager, if present.
VaultProperties.SessionLifecycle lifecycle = vaultProperties.getSession().getLifecycle();
if (lifecycle.isEnabled()) {
RestTemplate restTemplate = bootstrap.get(RestTemplateFactory.class).create();
LifecycleAwareSessionManagerSupport.RefreshTrigger trigger = new LifecycleAwareSessionManagerSupport.FixedTimeoutRefreshTrigger(
lifecycle.getRefreshBeforeExpiry(), lifecycle.getExpiryThreshold());
return new LifecycleAwareSessionManager(clientAuthentication,
ctx.get(TaskSchedulerWrapper.class).getTaskScheduler(), restTemplate, trigger);
}
return new SimpleSessionManager(clientAuthentication);
});
bootstrap.registerIfAbsent(VaultTemplate.class,
ctx -> new VaultTemplate(bootstrap.get(RestTemplateBuilder.class),
bootstrap.get(SessionManager.class)));
}
registerVaultConfigTemplate(bootstrap, vaultProperties);
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
registerSecretLeaseContainer(bootstrap, new VaultConfiguration(vaultProperties));
}
VaultProperties.ConfigLifecycle lifecycle = vaultProperties.getConfig().getLifecycle();
return loadConfigData(location, bootstrap, vaultProperties);
}
bootstrap.registerIfAbsent(SecretLeaseContainer.class, ctx -> {
SecretLeaseContainer container = new SecretLeaseContainer(ctx.get(VaultTemplate.class),
ctx.get(TaskSchedulerWrapper.class).getTaskScheduler());
private ConfigData loadConfigData(VaultConfigLocation location, ConfigurableBootstrapContext bootstrap,
VaultProperties vaultProperties) {
customizeContainer(lifecycle, container);
// This is to destroy bootstrap resources
// otherwise, the bootstrap context is not shut down cleanly
// TODO
// this.applicationContext.registerShutdownHook();
try {
container.afterPropertiesSet();
}
catch (Exception e) {
ReflectionUtils.rethrowRuntimeException(e);
}
container.start();
return container;
});
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
RequestedSecret secret = getRequestedSecret(location.getSecretBackendMetadata());
@@ -169,14 +130,111 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
bootstrap.get(SecretLeaseContainer.class), secret, location.getSecretBackendMetadata())));
}
return new ConfigData(Collections.singleton(createLeasingPropertySource(
bootstrap.get(SecretLeaseContainer.class), secret, location.getSecretBackendMetadata())));
return createConfigData(() -> createLeasingPropertySource(bootstrap.get(SecretLeaseContainer.class), secret,
location.getSecretBackendMetadata()));
}
return createConfigData(() -> {
VaultConfigTemplate configTemplate = bootstrap.get(VaultConfigTemplate.class);
return createVaultPropertySource(configTemplate, vaultProperties.isFailFast(),
location.getSecretBackendMetadata());
});
}
private void registerImperativeInfrastructure(ConfigurableBootstrapContext bootstrap,
VaultProperties vaultProperties) {
ImperativeInfrastructure infra = new ImperativeInfrastructure(bootstrap, vaultProperties);
infra.registerClientHttpRequestFactoryWrapper();
infra.registerRestTemplateBuilder();
infra.registerVaultRestTemplateFactory();
VaultProperties.AuthenticationMethod authentication = vaultProperties.getAuthentication();
if (authentication == VaultProperties.AuthenticationMethod.NONE) {
registerIfAbsent(bootstrap, "vaultTemplate", VaultTemplate.class,
ctx -> new VaultTemplate(ctx.get(RestTemplateBuilder.class)));
}
else {
infra.registerClientAuthentication();
if (!REGISTER_REACTIVE_INFRASTRUCTURE) {
infra.registerVaultSessionManager();
}
registerIfAbsent(bootstrap, "vaultTemplate", VaultTemplate.class,
ctx -> new VaultTemplate(bootstrap.get(RestTemplateBuilder.class),
bootstrap.get(SessionManager.class)));
}
}
private void registerReactiveInfrastructure(ConfigurableBootstrapContext bootstrap,
VaultProperties vaultProperties) {
ReactiveInfrastructure reactiveInfrastructure = new ReactiveInfrastructure(bootstrap, vaultProperties);
reactiveInfrastructure.registerClientHttpConnector();
reactiveInfrastructure.registerWebClientBuilder();
reactiveInfrastructure.registerWebClientFactory();
VaultProperties.AuthenticationMethod authentication = vaultProperties.getAuthentication();
if (authentication == VaultProperties.AuthenticationMethod.NONE) {
registerIfAbsent(bootstrap, "reactiveVaultTemplate", ReactiveVaultTemplate.class,
ctx -> new ReactiveVaultTemplate(ctx.get(WebClientBuilder.class)));
}
else {
reactiveInfrastructure.registerTokenSupplier();
reactiveInfrastructure.registerReactiveSessionManager();
reactiveInfrastructure.registerSessionManager();
registerIfAbsent(bootstrap, "reactiveVaultTemplate", ReactiveVaultTemplate.class,
ctx -> new ReactiveVaultTemplate(bootstrap.get(WebClientBuilder.class),
bootstrap.get(ReactiveSessionManager.class)));
}
}
static ConfigData createConfigData(Supplier<PropertySource<?>> propertySourceSupplier) {
return new ConfigData(Collections.singleton(propertySourceSupplier.get()));
}
private void registerVaultConfigTemplate(ConfigurableBootstrapContext bootstrap, VaultProperties vaultProperties) {
bootstrap.registerIfAbsent(VaultConfigTemplate.class,
ctx -> new VaultConfigTemplate(ctx.get(VaultTemplate.class), vaultProperties));
VaultConfigTemplate configTemplate = bootstrap.get(VaultConfigTemplate.class);
return new ConfigData(Collections.singletonList(createVaultPropertySource(configTemplate,
vaultProperties.isFailFast(), location.getSecretBackendMetadata())));
}
private void registerVaultTaskScheduler(ConfigurableBootstrapContext bootstrap) {
registerIfAbsent(bootstrap, "vaultTaskScheduler", TaskSchedulerWrapper.class, () -> {
ThreadPoolTaskScheduler scheduler = VaultConfiguration.createScheduler();
scheduler.afterPropertiesSet();
// avoid double-initialization
return new TaskSchedulerWrapper(scheduler, false);
}, ConfigurableApplicationContext::registerShutdownHook);
}
private void registerSecretLeaseContainer(ConfigurableBootstrapContext bootstrap,
VaultConfiguration vaultConfiguration) {
registerIfAbsent(bootstrap, "secretLeaseContainer", SecretLeaseContainer.class, ctx -> {
SecretLeaseContainer container = vaultConfiguration.createSecretLeaseContainer(ctx.get(VaultTemplate.class),
() -> ctx.get(TaskSchedulerWrapper.class).getTaskScheduler());
try {
container.afterPropertiesSet();
}
catch (Exception e) {
ReflectionUtils.rethrowRuntimeException(e);
}
container.start();
return container;
}, ConfigurableApplicationContext::registerShutdownHook);
}
private PropertySource<?> createVaultPropertySource(VaultConfigOperations configOperations, boolean failFast,
@@ -250,73 +308,203 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
return RequestedSecret.renewable(accessor.getPath());
}
static void customizeContainer(VaultProperties.ConfigLifecycle lifecycle, SecretLeaseContainer container) {
if (lifecycle.isEnabled()) {
if (lifecycle.getMinRenewal() != null) {
container.setMinRenewal(lifecycle.getMinRenewal());
}
if (lifecycle.getExpiryThreshold() != null) {
container.setExpiryThreshold(lifecycle.getExpiryThreshold());
}
if (lifecycle.getLeaseEndpoints() != null) {
container.setLeaseEndpoints(lifecycle.getLeaseEndpoints());
}
}
static <T> void registerIfAbsent(ConfigurableBootstrapContext bootstrap, String beanName, Class<T> instanceType,
Supplier<T> instanceSupplier) {
registerIfAbsent(bootstrap, beanName, instanceType, ctx -> instanceSupplier.get(), ctx -> {
});
}
static class ImperativeConfiguration {
static <T> void registerIfAbsent(ConfigurableBootstrapContext bootstrap, String beanName, Class<T> instanceType,
Supplier<T> instanceSupplier, Consumer<ConfigurableApplicationContext> contextCustomizer) {
registerIfAbsent(bootstrap, beanName, instanceType, ctx -> instanceSupplier.get(), contextCustomizer);
}
static <T> void registerIfAbsent(ConfigurableBootstrapContext bootstrap, String beanName, Class<T> instanceType,
Function<BootstrapContext, T> instanceSupplier) {
registerIfAbsent(bootstrap, beanName, instanceType, instanceSupplier, ctx -> {
});
}
static <T> void registerIfAbsent(ConfigurableBootstrapContext bootstrap, String beanName, Class<T> instanceType,
Function<BootstrapContext, T> instanceSupplier,
Consumer<ConfigurableApplicationContext> contextCustomizer) {
bootstrap.registerIfAbsent(instanceType, instanceSupplier::apply);
bootstrap.addCloseListener(event -> {
GenericApplicationContext gac = (GenericApplicationContext) event.getApplicationContext();
contextCustomizer.accept(gac);
T instance = event.getBootstrapContext().get(instanceType);
gac.registerBean(beanName, instanceType, () -> instance);
});
}
/**
* Support class to register imperative infrastructure bootstrap instances and beans.
*
* Mirrors {@link VaultAutoConfiguration}.
*/
static class ImperativeInfrastructure {
private final ConfigurableBootstrapContext bootstrap;
private final VaultProperties vaultProperties;
private final VaultConfiguration configuration;
private final VaultEndpointProvider endpointProvider;
ImperativeConfiguration(VaultProperties vaultProperties) {
ImperativeInfrastructure(ConfigurableBootstrapContext bootstrap, VaultProperties vaultProperties) {
this.bootstrap = bootstrap;
this.vaultProperties = vaultProperties;
this.endpointProvider = SimpleVaultEndpointProvider
.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties));
this.configuration = new VaultConfiguration(vaultProperties);
this.endpointProvider = SimpleVaultEndpointProvider.of(this.configuration.createVaultEndpoint());
}
public RestTemplateBuilder createRestTemplateBuilder(ClientHttpRequestFactory requestFactory) {
void registerClientHttpRequestFactoryWrapper() {
registerIfAbsent(this.bootstrap, "clientHttpRequestFactoryWrapper", ClientFactoryWrapper.class, () -> {
RestTemplateBuilder builder = RestTemplateBuilder.builder().requestFactory(requestFactory)
.endpointProvider(this.endpointProvider);
ClientHttpRequestFactory factory = this.configuration.createClientHttpRequestFactory();
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
}
// early initialization
try {
new ClientFactoryWrapper(factory).afterPropertiesSet();
}
catch (Exception e) {
ReflectionUtils.rethrowRuntimeException(e);
}
return builder;
return new NonInitializingClientFactoryWrapper(factory);
});
}
void registerRestTemplateBuilder() {
// not a bean
this.bootstrap.registerIfAbsent(RestTemplateBuilder.class,
ctx -> this.configuration.createRestTemplateBuilder(
ctx.get(ClientFactoryWrapper.class).getClientHttpRequestFactory(), this.endpointProvider,
Collections.emptyList(), Collections.emptyList()));
}
void registerVaultRestTemplateFactory() {
registerIfAbsent(this.bootstrap, "vaultRestTemplateFactory", RestTemplateFactory.class,
ctx -> new DefaultRestTemplateFactory(
ctx.get(ClientFactoryWrapper.class).getClientHttpRequestFactory(),
requestFactory -> this.configuration.createRestTemplateBuilder(requestFactory,
this.endpointProvider, Collections.emptyList(), Collections.emptyList())));
}
void registerClientAuthentication() {
registerIfAbsent(this.bootstrap, "clientAuthentication", ClientAuthentication.class, ctx -> {
ClientHttpRequestFactory factory = this.bootstrap.get(ClientFactoryWrapper.class)
.getClientHttpRequestFactory();
RestTemplate externalRestTemplate = new RestTemplate(factory);
ClientAuthenticationFactory authenticationFactory = new ClientAuthenticationFactory(
this.vaultProperties, this.bootstrap.get(RestTemplateFactory.class).create(),
externalRestTemplate);
return authenticationFactory.createClientAuthentication();
});
}
void registerVaultSessionManager() {
registerIfAbsent(this.bootstrap, "vaultSessionManager", SessionManager.class,
ctx -> this.configuration.createSessionManager(ctx.get(ClientAuthentication.class),
() -> ctx.get(TaskSchedulerWrapper.class).getTaskScheduler(),
ctx.get(RestTemplateFactory.class)));
}
}
// TODO
static class ReactiveConfiguration {
/**
* Support class to register reactive infrastructure bootstrap instances and beans.
* Mirrors {@link VaultReactiveAutoConfiguration}.
*/
static class ReactiveInfrastructure {
private final VaultProperties vaultProperties;
private final ConfigurableBootstrapContext bootstrap;
private final VaultReactiveConfiguration configuration;
private final VaultEndpointProvider endpointProvider;
ReactiveConfiguration(VaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
ReactiveInfrastructure(ConfigurableBootstrapContext bootstrap, VaultProperties vaultProperties) {
this.bootstrap = bootstrap;
this.configuration = new VaultReactiveConfiguration(vaultProperties);
this.endpointProvider = SimpleVaultEndpointProvider
.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties));
.of(new VaultConfiguration(vaultProperties).createVaultEndpoint());
}
public WebClientBuilder createRestTemplateBuilder(ClientHttpConnector connector) {
void registerClientHttpConnector() {
// not a bean
this.bootstrap.registerIfAbsent(ClientHttpConnector.class,
ctx -> this.configuration.createClientHttpConnector());
}
WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector)
.endpointProvider(this.endpointProvider);
public void registerWebClientBuilder() {
// not a bean
this.bootstrap.registerIfAbsent(WebClientBuilder.class,
ctx -> this.configuration.createWebClientBuilder(ctx.get(ClientHttpConnector.class),
this.endpointProvider, Collections.emptyList()));
}
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
}
void registerWebClientFactory() {
registerIfAbsent(this.bootstrap, "vaultWebClientFactory", WebClientFactory.class,
ctx -> new DefaultWebClientFactory(ctx.get(ClientHttpConnector.class),
connector -> this.configuration.createWebClientBuilder(connector, this.endpointProvider,
Collections.emptyList())));
}
return builder;
void registerTokenSupplier() {
registerIfAbsent(this.bootstrap, "vaultTokenSupplier", VaultTokenSupplier.class,
ctx -> this.configuration.createVaultTokenSupplier(ctx.get(WebClientFactory.class), () -> {
if (this.bootstrap.isRegistered(AuthenticationStepsFactory.class)) {
return this.bootstrap.get(AuthenticationStepsFactory.class);
}
return null;
}, () -> {
if (this.bootstrap.isRegistered(ClientAuthentication.class)) {
return this.bootstrap.get(ClientAuthentication.class);
}
return null;
}));
}
void registerReactiveSessionManager() {
registerIfAbsent(this.bootstrap, "reactiveVaultSessionManager", ReactiveSessionManager.class,
ctx -> this.configuration.createReactiveSessionManager(ctx.get(VaultTokenSupplier.class),
() -> ctx.get(TaskSchedulerWrapper.class).getTaskScheduler(),
ctx.get(WebClientFactory.class)));
}
void registerSessionManager() {
registerIfAbsent(this.bootstrap, "vaultSessionManager", SessionManager.class,
ctx -> this.configuration.createSessionManager(ctx.get(ReactiveSessionManager.class)));
}
}
/**
* Wrapper for {@link ClientHttpRequestFactory} that suppresses
* {@link #afterPropertiesSet()} to avoid double-initialization.
*/
private static class NonInitializingClientFactoryWrapper extends ClientFactoryWrapper {
NonInitializingClientFactoryWrapper(ClientHttpRequestFactory clientHttpRequestFactory) {
super(clientHttpRequestFactory);
}
@Override
public void afterPropertiesSet() {
}
}

View File

@@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
@@ -37,8 +39,41 @@ import org.springframework.util.ReflectionUtils;
/**
* {@link ConfigDataLocationResolver} for Vault resolving {@link VaultConfigLocation}
* using the {@code vault:} prefix.
* <p>
* Resolution considers contextual locations as we as default locations. Contextual
* locations such as {@code vault:secret/my-application} are considered to be context
* paths for the Key-Value secrets backend. Using a default location {@code vault:}
* imports all enabled {@link VaultSecretBackendDescriptor secret backends } by creating
* {@link SecretBackendMetadata} from {@link SecretBackendMetadataFactory}. Note that both
* types,{@link VaultSecretBackendDescriptor} and {@link SecretBackendMetadataFactory} are
* resolved through {@link SpringFactoriesLoader spring.factories} to allow optional
* presence/absence on the class path.
* <p>
* Mixing paths
* ({@code spring.config.import=vault:,vault:secret/my-application,vault:secret/other-location})
* is possible as each config location creates an individual {@link VaultConfigLocation}.
* By enabling/disabling {@link VaultSecretBackendDescriptor#isEnabled() a
* VaultSecretBackendDescriptor}, you can control the amount of secret backends that are
* imported through the default location.
* <p>
* You can customize the default location capabilities by registering
* {@link VaultConfigurer} in the {@link BootstrapRegistry}. For example:
*
* <pre class="code">
* VaultConfigurer configurer = …;
* SpringApplication application = …;
*
* application.addBootstrapper(registy -> register(VaultConfigurer.class, context -> configurer));
* </pre>
* <p>
* Registers also {@link VaultProperties} in the {@link BootstrapRegistry} that is
* required later on by {@link VaultConfigDataLoader}.
*
* @author Mark Paluch
* @since 3.0
* @see VaultConfigurer
* @see BootstrapRegistry
* @see VaultConfigDataLoader
*/
public class VaultConfigDataLocationResolver implements ConfigDataLocationResolver<VaultConfigLocation> {
@@ -61,78 +96,127 @@ public class VaultConfigDataLocationResolver implements ConfigDataLocationResolv
public List<VaultConfigLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context, String location,
boolean optional, Profiles profiles) throws ConfigDataLocationNotFoundException {
context.getBootstrapContext().registerIfAbsent(VaultProperties.class,
ignore -> context.getBinder().bindOrCreate(VaultProperties.PREFIX, VaultProperties.class));
if (!location.startsWith(VaultConfigLocation.VAULT_PREFIX)) {
return Collections.emptyList();
}
if (location.trim().equals(VaultConfigLocation.VAULT_PREFIX)) {
List<VaultSecretBackendDescriptor> descriptors = findDescriptors(context);
List<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> factories = (List) SpringFactoriesLoader
.loadFactories(SecretBackendMetadataFactory.class, getClass().getClassLoader());
PropertySourceLocatorConfigurationFactory factory = new PropertySourceLocatorConfigurationFactory(
Collections.emptyList(), descriptors, factories);
VaultKeyValueBackendProperties kvProperties = context.getBinder()
.bindOrCreate(VaultKeyValueBackendProperties.PREFIX, VaultKeyValueBackendProperties.class);
kvProperties.setApplicationName(getApplicationName(context.getBinder()));
kvProperties.setProfiles(profiles.getActive());
PropertySourceLocatorConfiguration configuration = factory
.getPropertySourceConfiguration(Collections.singletonList(kvProperties));
Collection<SecretBackendMetadata> secretBackends = configuration.getSecretBackends();
List<SecretBackendMetadata> sorted = new ArrayList<>(secretBackends);
AnnotationAwareOrderComparator.sort(sorted);
registerVaultProperties(context);
if (location.equals(VaultConfigLocation.VAULT_PREFIX)
|| location.equals(VaultConfigLocation.VAULT_PREFIX + "//")) {
List<SecretBackendMetadata> sorted = getSecretBackends(context, profiles);
return sorted.stream().map(it -> new VaultConfigLocation(it, optional)).collect(Collectors.toList());
}
String contextPath = location.substring(VaultConfigLocation.VAULT_PREFIX.length());
while (contextPath.startsWith("/")) {
contextPath = contextPath.substring(1);
}
return Collections.singletonList(new VaultConfigLocation(contextPath, optional));
}
private static String getApplicationName(Binder binder) {
private static void registerVaultProperties(ConfigDataLocationResolverContext context) {
return binder.bind("spring.cloud.vault.application-name", String.class)
.orElseGet(() -> binder.bind("spring.application-name", String.class).orElse(""));
context.getBootstrapContext().registerIfAbsent(VaultProperties.class, ignore -> {
return context.getBinder().bindOrCreate(VaultProperties.PREFIX, VaultProperties.class);
});
}
private List<VaultSecretBackendDescriptor> findDescriptors(ConfigDataLocationResolverContext context) {
private List<SecretBackendMetadata> getSecretBackends(ConfigDataLocationResolverContext context,
Profiles profiles) {
List<VaultSecretBackendDescriptor> descriptors = findDescriptors(context.getBinder());
List<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> factories = getSecretBackendMetadataFactories();
Collection<VaultConfigurer> vaultConfigurers = getVaultConfigurers(context.getBootstrapContext());
PropertySourceLocatorConfigurationFactory factory = new PropertySourceLocatorConfigurationFactory(
vaultConfigurers, descriptors, factories);
VaultKeyValueBackendProperties kvProperties = getKeyValueProperties(context, profiles);
PropertySourceLocatorConfiguration configuration = factory.getPropertySourceConfiguration(kvProperties);
Collection<SecretBackendMetadata> secretBackends = configuration.getSecretBackends();
List<SecretBackendMetadata> sorted = new ArrayList<>(secretBackends);
AnnotationAwareOrderComparator.sort(sorted);
return sorted;
}
private static Collection<VaultConfigurer> getVaultConfigurers(ConfigurableBootstrapContext bootstrapContext) {
Collection<VaultConfigurer> vaultConfigurers = new ArrayList<>(1);
if (bootstrapContext.isRegistered(VaultConfigurer.class)) {
vaultConfigurers.add(bootstrapContext.get(VaultConfigurer.class));
}
return vaultConfigurers;
}
private static VaultKeyValueBackendProperties getKeyValueProperties(ConfigDataLocationResolverContext context,
Profiles profiles) {
VaultKeyValueBackendProperties kvProperties = context.getBinder()
.bindOrCreate(VaultKeyValueBackendProperties.PREFIX, VaultKeyValueBackendProperties.class);
Binder binder = context.getBinder();
kvProperties.setApplicationName(binder.bind("spring.cloud.vault.application-name", String.class)
.orElseGet(() -> binder.bind("spring.application-name", String.class).orElse("")));
kvProperties.setProfiles(profiles.getActive());
return kvProperties;
}
private static List<VaultSecretBackendDescriptor> findDescriptors(Binder binder) {
List<String> descriptorClasses = SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptor.class,
getClass().getClassLoader());
VaultConfigDataLocationResolver.class.getClassLoader());
List<VaultSecretBackendDescriptor> descriptors = new ArrayList<>(descriptorClasses.size());
try {
for (String className : descriptorClasses) {
for (String className : descriptorClasses) {
Class<VaultSecretBackendDescriptor> descriptorClass = (Class<VaultSecretBackendDescriptor>) ClassUtils
.forName(className, getClass().getClassLoader());
Class<VaultSecretBackendDescriptor> descriptorClass = loadClass(className);
MergedAnnotations annotations = MergedAnnotations.from(descriptorClass);
if (annotations.isPresent(ConfigurationProperties.class)) {
MergedAnnotations annotations = MergedAnnotations.from(descriptorClass);
if (annotations.isPresent(ConfigurationProperties.class)) {
String prefix = annotations.get(ConfigurationProperties.class).getString("prefix");
VaultSecretBackendDescriptor hydratedDescriptor = context.getBinder().bindOrCreate(prefix,
descriptorClass);
descriptors.add(hydratedDescriptor);
}
else {
throw new IllegalStateException(String.format(
"VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties",
className));
}
String prefix = annotations.get(ConfigurationProperties.class).getString("prefix");
VaultSecretBackendDescriptor hydratedDescriptor = binder.bindOrCreate(prefix, descriptorClass);
descriptors.add(hydratedDescriptor);
}
else {
throw new IllegalStateException(String.format(
"VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties", className));
}
}
catch (ReflectiveOperationException e) {
ReflectionUtils.rethrowRuntimeException(e);
}
return descriptors;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static List<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> getSecretBackendMetadataFactories() {
return (List) SpringFactoriesLoader.loadFactories(SecretBackendMetadataFactory.class,
VaultConfigDataLocationResolver.class.getClassLoader());
}
@SuppressWarnings("unchecked")
private static Class<VaultSecretBackendDescriptor> loadClass(String className) {
try {
return (Class<VaultSecretBackendDescriptor>) ClassUtils.forName(className,
VaultConfigDataLocationResolver.class.getClassLoader());
}
catch (ReflectiveOperationException e) {
ReflectionUtils.rethrowRuntimeException(e);
// should never happen.
return null;
}
}
}

View File

@@ -17,10 +17,16 @@
package org.springframework.cloud.vault.config;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Vault-specific implementation for a {@link ConfigDataLocation}. Consists of a
* {@link SecretBackendMetadata}.
*
* @author Mark Paluch
* @since 3.0
* @see SecretBackendMetadata
*/
public class VaultConfigLocation extends ConfigDataLocation {
@@ -34,11 +40,17 @@ public class VaultConfigLocation extends ConfigDataLocation {
private final boolean optional;
public VaultConfigLocation(String contextPath, boolean optional) {
Assert.hasText(contextPath, "Context path must not be empty");
this.secretBackendMetadata = KeyValueSecretBackendMetadata.create(contextPath);
this.optional = optional;
}
public VaultConfigLocation(SecretBackendMetadata secretBackendMetadata, boolean optional) {
Assert.notNull(secretBackendMetadata, "SecretBackendMetadata must not be null");
this.secretBackendMetadata = secretBackendMetadata;
this.optional = optional;
}
@@ -80,8 +92,7 @@ public class VaultConfigLocation extends ConfigDataLocation {
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [name='").append(this.secretBackendMetadata.getName()).append('\'');
sb.append(", path='").append(this.secretBackendMetadata.getPath()).append('\'');
sb.append(" [path='").append(this.secretBackendMetadata.getPath()).append('\'');
sb.append(", optional=").append(this.optional);
sb.append(']');
return sb.toString();

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2018-2020 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.vault.config;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.cloud.vault.config.VaultProperties.Ssl;
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.LifecycleAwareSessionManagerSupport;
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.RestTemplateFactory;
import org.springframework.vault.client.RestTemplateRequestCustomizer;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.core.VaultOperations;
import org.springframework.vault.core.lease.SecretLeaseContainer;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.web.client.RestTemplate;
/**
* Support class for Vault configuration providing utility methods.
*
* @author Mark Paluch
* @since 3.0
*/
final class VaultConfiguration {
private final VaultProperties vaultProperties;
VaultConfiguration(VaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
}
/**
* Create a {@link SslConfiguration} given {@link Ssl SSL properties}.
* @param ssl the SSL properties.
* @return the SSL configuration.
*/
static SslConfiguration createSslConfiguration(Ssl ssl) {
if (ssl == null) {
return SslConfiguration.unconfigured();
}
KeyStoreConfiguration keyStore = KeyStoreConfiguration.unconfigured();
KeyStoreConfiguration trustStore = KeyStoreConfiguration.unconfigured();
if (ssl.getKeyStore() != null) {
if (StringUtils.hasText(ssl.getKeyStorePassword())) {
keyStore = KeyStoreConfiguration.of(ssl.getKeyStore(), ssl.getKeyStorePassword().toCharArray());
}
else {
keyStore = KeyStoreConfiguration.of(ssl.getKeyStore());
}
}
if (ssl.getTrustStore() != null) {
if (StringUtils.hasText(ssl.getTrustStorePassword())) {
trustStore = KeyStoreConfiguration.of(ssl.getTrustStore(), ssl.getTrustStorePassword().toCharArray());
}
else {
trustStore = KeyStoreConfiguration.of(ssl.getTrustStore());
}
}
return new SslConfiguration(keyStore, trustStore);
}
ClientHttpRequestFactory createClientHttpRequestFactory() {
ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(this.vaultProperties.getConnectionTimeout()),
Duration.ofMillis(this.vaultProperties.getReadTimeout()));
SslConfiguration sslConfiguration = VaultConfiguration.createSslConfiguration(this.vaultProperties.getSsl());
return ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration);
}
/**
* Create a {@link VaultEndpoint} from {@link VaultProperties}.
* @return the endpoint.
*/
VaultEndpoint createVaultEndpoint() {
if (StringUtils.hasText(this.vaultProperties.getUri())) {
return VaultEndpoint.from(URI.create(this.vaultProperties.getUri()));
}
VaultEndpoint vaultEndpoint = new VaultEndpoint();
vaultEndpoint.setHost(this.vaultProperties.getHost());
vaultEndpoint.setPort(this.vaultProperties.getPort());
vaultEndpoint.setScheme(this.vaultProperties.getScheme());
return vaultEndpoint;
}
RestTemplateBuilder createRestTemplateBuilder(ClientHttpRequestFactory requestFactory,
VaultEndpointProvider endpointProvider, List<RestTemplateCustomizer> customizers,
List<RestTemplateRequestCustomizer<?>> requestCustomizers) {
RestTemplateBuilder builder = RestTemplateBuilder.builder().requestFactory(requestFactory)
.endpointProvider(endpointProvider);
customizers.forEach(builder::customizers);
requestCustomizers.forEach(builder::requestCustomizers);
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
}
return builder;
}
SessionManager createSessionManager(ClientAuthentication clientAuthentication,
Supplier<TaskScheduler> taskSchedulerSupplier, RestTemplateFactory restTemplateFactory) {
VaultProperties.SessionLifecycle lifecycle = this.vaultProperties.getSession().getLifecycle();
if (lifecycle.isEnabled()) {
RestTemplate restTemplate = restTemplateFactory.create();
LifecycleAwareSessionManagerSupport.RefreshTrigger trigger = new LifecycleAwareSessionManagerSupport.FixedTimeoutRefreshTrigger(
lifecycle.getRefreshBeforeExpiry(), lifecycle.getExpiryThreshold());
return new LifecycleAwareSessionManager(clientAuthentication, taskSchedulerSupplier.get(), restTemplate,
trigger);
}
return new SimpleSessionManager(clientAuthentication);
}
SecretLeaseContainer createSecretLeaseContainer(VaultOperations vaultOperations,
Supplier<TaskScheduler> taskSchedulerSupplier) {
VaultProperties.ConfigLifecycle lifecycle = this.vaultProperties.getConfig().getLifecycle();
SecretLeaseContainer container = new SecretLeaseContainer(vaultOperations, taskSchedulerSupplier.get());
customizeContainer(lifecycle, container);
return container;
}
static ThreadPoolTaskScheduler createScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(2);
threadPoolTaskScheduler.setDaemon(true);
threadPoolTaskScheduler.setThreadNamePrefix("Spring-Cloud-Vault-");
return threadPoolTaskScheduler;
}
static void customizeContainer(VaultProperties.ConfigLifecycle lifecycle, SecretLeaseContainer container) {
if (lifecycle.isEnabled()) {
if (lifecycle.getMinRenewal() != null) {
container.setMinRenewal(lifecycle.getMinRenewal());
}
if (lifecycle.getExpiryThreshold() != null) {
container.setExpiryThreshold(lifecycle.getExpiryThreshold());
}
if (lifecycle.getLeaseEndpoints() != null) {
container.setLeaseEndpoints(lifecycle.getLeaseEndpoints());
}
}
}
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright 2018-2020 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.vault.config;
import java.net.URI;
import java.time.Duration;
import org.springframework.cloud.vault.config.VaultProperties.Ssl;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
/**
* Support class for Vault configuration providing utility methods.
*
* @author Mark Paluch
* @since 2.1
*/
final class VaultConfigurationUtil {
private VaultConfigurationUtil() {
}
static ClientHttpRequestFactory createClientHttpRequestFactory(VaultProperties vaultProperties) {
ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(vaultProperties.getConnectionTimeout()),
Duration.ofMillis(vaultProperties.getReadTimeout()));
SslConfiguration sslConfiguration = VaultConfigurationUtil.createSslConfiguration(vaultProperties.getSsl());
return ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration);
}
/**
* Create a {@link SslConfiguration} given {@link Ssl SSL properties}.
* @param ssl the SSL properties.
* @return the SSL configuration.
*/
static SslConfiguration createSslConfiguration(Ssl ssl) {
if (ssl == null) {
return SslConfiguration.unconfigured();
}
KeyStoreConfiguration keyStore = KeyStoreConfiguration.unconfigured();
KeyStoreConfiguration trustStore = KeyStoreConfiguration.unconfigured();
if (ssl.getKeyStore() != null) {
if (StringUtils.hasText(ssl.getKeyStorePassword())) {
keyStore = KeyStoreConfiguration.of(ssl.getKeyStore(), ssl.getKeyStorePassword().toCharArray());
}
else {
keyStore = KeyStoreConfiguration.of(ssl.getKeyStore());
}
}
if (ssl.getTrustStore() != null) {
if (StringUtils.hasText(ssl.getTrustStorePassword())) {
trustStore = KeyStoreConfiguration.of(ssl.getTrustStore(), ssl.getTrustStorePassword().toCharArray());
}
else {
trustStore = KeyStoreConfiguration.of(ssl.getTrustStore());
}
}
return new SslConfiguration(keyStore, trustStore);
}
/**
* Create a {@link VaultEndpoint} given {@link VaultProperties}.
* @param vaultProperties the Vault properties.
* @return the endpoint.
*/
static VaultEndpoint createVaultEndpoint(VaultProperties vaultProperties) {
if (StringUtils.hasText(vaultProperties.getUri())) {
return VaultEndpoint.from(URI.create(vaultProperties.getUri()));
}
VaultEndpoint vaultEndpoint = new VaultEndpoint();
vaultEndpoint.setHost(vaultProperties.getHost());
vaultEndpoint.setPort(vaultProperties.getPort());
vaultEndpoint.setScheme(vaultProperties.getScheme());
return vaultEndpoint;
}
}

View File

@@ -22,6 +22,7 @@ import java.time.Duration;
import javax.validation.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
@@ -1043,6 +1044,7 @@ public class VaultProperties implements EnvironmentAware {
private ConfigLifecycle lifecycle = new ConfigLifecycle();
@DeprecatedConfigurationProperty(reason = "Only required for deprecated Bootstrap Context usage")
public int getOrder() {
return this.order;
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2018-2020 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.vault.config;
import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.AuthenticationStepsOperator;
import org.springframework.vault.authentication.CachingVaultTokenSupplier;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ReactiveLifecycleAwareSessionManager;
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.ClientHttpConnectorFactory;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.client.WebClientCustomizer;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Support class for Vault configuration providing utility methods.
*
* @author Mark Paluch
* @since 3.0
*/
final class VaultReactiveConfiguration {
private final VaultProperties vaultProperties;
VaultReactiveConfiguration(VaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
}
ClientHttpConnector createClientHttpConnector() {
ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(this.vaultProperties.getConnectionTimeout()),
Duration.ofMillis(this.vaultProperties.getReadTimeout()));
SslConfiguration sslConfiguration = VaultConfiguration.createSslConfiguration(this.vaultProperties.getSsl());
return ClientHttpConnectorFactory.create(clientOptions, sslConfiguration);
}
WebClientBuilder createWebClientBuilder(ClientHttpConnector connector, VaultEndpointProvider endpointProvider,
List<WebClientCustomizer> customizers) {
WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector)
.endpointProvider(endpointProvider);
customizers.forEach(builder::customizers);
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
}
return builder;
}
VaultTokenSupplier createVaultTokenSupplier(WebClientFactory webClientFactory,
Supplier<AuthenticationStepsFactory> stepsFactorySupplier,
Supplier<ClientAuthentication> clientAuthenticationSupplier) {
AuthenticationStepsFactory authenticationStepsFactory = stepsFactorySupplier.get();
if (authenticationStepsFactory != null) {
return createAuthenticationStepsOperator(authenticationStepsFactory, webClientFactory);
}
ClientAuthentication clientAuthentication = clientAuthenticationSupplier.get();
if (clientAuthentication != null) {
if (clientAuthentication instanceof TokenAuthentication) {
TokenAuthentication authentication = (TokenAuthentication) clientAuthentication;
return () -> Mono.just(authentication.login());
}
if (clientAuthentication instanceof AuthenticationStepsFactory) {
return createAuthenticationStepsOperator((AuthenticationStepsFactory) clientAuthentication,
webClientFactory);
}
throw new IllegalStateException(String.format("Cannot construct VaultTokenSupplier from %s. "
+ "ClientAuthentication must implement AuthenticationStepsFactory or be TokenAuthentication",
clientAuthentication));
}
throw new IllegalStateException(
"Cannot construct VaultTokenSupplier. Please configure VaultTokenSupplier bean named vaultTokenSupplier.");
}
private VaultTokenSupplier createAuthenticationStepsOperator(AuthenticationStepsFactory factory,
WebClientFactory webClientFactory) {
WebClient webClient = webClientFactory.create();
return new AuthenticationStepsOperator(factory.getAuthenticationSteps(), webClient);
}
SessionManager createSessionManager(ReactiveSessionManager sessionManager) {
return new ReactiveSessionManagerAdapter(sessionManager);
}
ReactiveSessionManager createReactiveSessionManager(VaultTokenSupplier vaultTokenSupplier,
Supplier<TaskScheduler> taskScheduler, WebClientFactory webClientFactory) {
VaultProperties.SessionLifecycle lifecycle = this.vaultProperties.getSession().getLifecycle();
if (lifecycle.isEnabled()) {
WebClient webClient = webClientFactory.create();
ReactiveLifecycleAwareSessionManager.RefreshTrigger trigger = new ReactiveLifecycleAwareSessionManager.FixedTimeoutRefreshTrigger(
lifecycle.getRefreshBeforeExpiry(), lifecycle.getExpiryThreshold());
return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier, taskScheduler.get(), webClient,
trigger);
}
return CachingVaultTokenSupplier.of(vaultTokenSupplier);
}
private static final class ReactiveSessionManagerAdapter implements SessionManager {
private final ReactiveSessionManager sessionManager;
private ReactiveSessionManagerAdapter(ReactiveSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public VaultToken getSessionToken() {
VaultToken token = this.sessionManager.getSessionToken().block();
Assert.state(token != null, "ReactiveSessionManager returned a null VaultToken");
return token;
}
}
}

View File

@@ -16,13 +16,23 @@
package org.springframework.cloud.vault.config;
import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.bootstrap.BootstrapConfiguration;
import org.springframework.context.ApplicationContext;
/**
* Interface to be implemented by objects that describe a Vault secret backend. Mainly for
* internal use within the framework.
*
* <p>
* Typically used by {@link SecretBackendMetadataFactory} to provide path and
* configuration to create a {@link SecretBackendMetadata} object.
* configuration to create a {@link SecretBackendMetadata} object. Instances are
* materialized through {@link Binder} and should be therefore annotated with
* {@link org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties}.
* Objects implementing this interface can be discovered either from the
* {@link ApplicationContext} when using {@link BootstrapConfiguration} (deprecated since
* 3.0) or {@code spring.factories} when using {@link ConfigDataLocationResolver}.
*
* @author Mark Paluch
* @see SecretBackendMetadataFactory

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2017-2020 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.
*/
/**
* Core Vault support classes.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.cloud.vault.config;

View File

@@ -1,5 +1,7 @@
# Auto-Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.vault.config.VaultReactiveAutoConfiguration,\
org.springframework.cloud.vault.config.VaultAutoConfiguration,\
org.springframework.cloud.vault.config.VaultHealthIndicatorAutoConfiguration
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\

View File

@@ -80,8 +80,7 @@ public class VaultBootstrapPropertySourceConfigurationTests {
SecretLeaseContainer secretLeaseContainer(VaultProperties properties) {
SecretLeaseContainer mock = mock(SecretLeaseContainer.class);
VaultBootstrapPropertySourceConfiguration.customizeContainer(properties.getConfig().getLifecycle(), mock);
VaultConfiguration.customizeContainer(properties.getConfig().getLifecycle(), mock);
return mock;
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2016-2020 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.vault.config;
import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.vault.util.IntegrationTestSupport;
import org.springframework.cloud.vault.util.Settings;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link VaultBootstrapper}.
*
* @author Mark Paluch
*/
public class VaultBootstrapperIntegrationTests extends IntegrationTestSupport {
private ConfigurableApplicationContext context;
@Before
public void before() {
this.vaultRule.prepare().getVaultOperations().write("secret/VaultBootstrapPropertySourceConfigurationTests",
Collections.singletonMap("default-key", "default"));
this.vaultRule.prepare().getVaultOperations().write("secret/customized",
Collections.singletonMap("key", "customized"));
SpringApplication application = new SpringApplication(Config.class);
application.setWebApplicationType(WebApplicationType.NONE);
application
.addBootstrapper(VaultBootstrapper.fromConfigurer(configurer -> configurer.add("secret/customized")));
this.context = application.run("--spring.application.name=VaultBootstrapPropertySourceConfigurationTests",
"--spring.config.import=vault:", "--spring.cloud.vault.token=" + Settings.token().getToken());
}
@Test
public void shouldApplyConfigurer() {
assertThat(this.context.getEnvironment().getProperty("default-key")).isNull();
assertThat(this.context.getEnvironment().getProperty("key")).isEqualTo("customized");
}
@After
public void after() {
if (this.context != null) {
this.context.close();
}
}
@SpringBootConfiguration(proxyBeanMethods = false)
@EnableAutoConfiguration
private static class Config {
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2019-2020 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.vault.config;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
import org.springframework.boot.context.config.Profiles;
import org.springframework.boot.context.properties.bind.Binder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link VaultConfigDataLocationResolver}.
*
* @author Mark Paluch
*/
public class VaultConfigDataLocationResolverUnitTests {
ConfigDataLocationResolverContext contextMock = mock(ConfigDataLocationResolverContext.class);
Profiles profilesMock = mock(Profiles.class);
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Before
public void before() {
when(this.contextMock.getBootstrapContext()).thenReturn(this.bootstrapContext);
when(this.contextMock.getBinder()).thenReturn(new Binder());
}
@Test
public void shouldDiscoverDefaultLocations() {
VaultConfigDataLocationResolver resolver = new VaultConfigDataLocationResolver();
when(this.profilesMock.getActive()).thenReturn(Arrays.asList("a", "b"));
assertThat(resolver.resolveProfileSpecific(this.contextMock, "vault:", false, this.profilesMock)).hasSize(3);
assertThat(resolver.resolveProfileSpecific(this.contextMock, "vault://", false, this.profilesMock)).hasSize(3);
}
@Test
public void shouldDiscoverContextualLocations() {
VaultConfigDataLocationResolver resolver = new VaultConfigDataLocationResolver();
List<VaultConfigLocation> locations = resolver.resolveProfileSpecific(this.contextMock,
"vault://my/context/path", false, this.profilesMock);
assertThat(locations).hasSize(1);
assertThat(locations.get(0)).hasToString("VaultConfigLocation [path='my/context/path', optional=false]");
}
}

View File

@@ -21,7 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,8 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = VaultConfigLoaderTests.TestApplication.class,
properties = { "spring.cloud.vault.uri=https://localhost:8200",
"spring.cloud.vault.application-name=config-data", "spring.config.import=vault:",
"spring.cloud.bootstrap.enabled=false" })
"spring.cloud.vault.application-name=config-data", "spring.config.import=vault:" })
public class VaultConfigLoaderTests {
@Value("${vault.value}")
@@ -96,11 +94,15 @@ public class VaultConfigLoaderTests {
}
@Test
@Ignore
public void shouldContainVaultBeans() {
assertThat(this.applicationContext.getBeanNamesForType(VaultTemplate.class)).isNotEmpty();
assertThat(this.applicationContext.getBeanNamesForType(LeasingVaultPropertySourceLocator.class)).isNotEmpty();
}
@Test
public void shouldNotRegisterPropertySourceLocator() {
assertThat(this.applicationContext.getBeanNamesForType(LeasingVaultPropertySourceLocator.class)).isEmpty();
}
@Test

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2017-2020 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.vault.config;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.vault.authentication.AuthenticationSteps;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.CachingVaultTokenSupplier;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link VaultReactiveAutoConfiguration}.
*
* @author Mark Paluch
*/
public class VaultReactiveAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(VaultReactiveAutoConfiguration.class));
@Test
public void shouldConfigureTemplate() {
this.contextRunner.withUserConfiguration(AuthenticationFactoryConfiguration.class)
.withPropertyValues("spring.cloud.vault.session.lifecycle.enabled=false").run(context -> {
assertThat(context).hasSingleBean(ReactiveVaultOperations.class);
assertThat(context).hasSingleBean(AuthenticationStepsFactory.class);
assertThat(context.getBean(SessionManager.class)).isNotNull()
.isNotInstanceOf(LifecycleAwareSessionManager.class)
.isNotInstanceOf(SimpleSessionManager.class);
assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty();
assertThat(context).hasSingleBean(WebClientFactory.class);
});
}
@Test
public void shouldNotConfigureIfHttpClientIsMissing() {
this.contextRunner.withUserConfiguration(AuthenticationFactoryConfiguration.class)
.withClassLoader(new FilteredClassLoader("reactor.netty.http.client.HttpClient")).run(context -> {
assertThat(context).doesNotHaveBean(ReactiveVaultOperations.class);
});
}
@Test
public void shouldConfigureTemplateWithTokenSupplier() {
this.contextRunner.withUserConfiguration(TokenSupplierConfiguration.class)
.withPropertyValues("spring.cloud.vault.session.lifecycle.enabled=false").run(context -> {
assertThat(context).hasSingleBean(ReactiveVaultOperations.class);
assertThat(context.getBean(SessionManager.class)).isNotNull()
.isNotInstanceOf(LifecycleAwareSessionManager.class)
.isNotInstanceOf(SimpleSessionManager.class);
assertThat(context).doesNotHaveBean(WebClient.class);
});
}
@Test
public void shouldNotConfigureReactiveSupport() {
this.contextRunner.withUserConfiguration(VaultAutoConfiguration.class)
.withPropertyValues("spring.cloud.vault.reactive.enabled=false", "spring.cloud.vault.token=foo")
.run(context -> {
assertThat(context).doesNotHaveBean(ReactiveVaultTemplate.class)
.doesNotHaveBean(ReactiveVaultOperations.class);
assertThat(context.getBean(SessionManager.class)).isInstanceOf(LifecycleAwareSessionManager.class);
});
}
@Test
public void sessionManagerBridgeShouldNotCacheTokens() {
this.contextRunner.withUserConfiguration(TokenSupplierConfiguration.class, CustomSessionManager.class)
.run(context -> {
SessionManager sessionManager = context.getBean(SessionManager.class);
assertThat(sessionManager.getSessionToken().getToken()).isEqualTo("token-1");
assertThat(sessionManager.getSessionToken().getToken()).isEqualTo("token-2");
});
}
@Test
public void shouldDisableSessionManagement() {
this.contextRunner
.withPropertyValues("spring.cloud.vault.kv.enabled=false", "spring.cloud.vault.token=foo",
"spring.cloud.vault.session.lifecycle.enabled=false")
.withBean("vaultTokenSupplier", VaultTokenSupplier.class, () -> Mono::empty)
.withBean("taskSchedulerWrapper", VaultAutoConfiguration.TaskSchedulerWrapper.class,
() -> new VaultAutoConfiguration.TaskSchedulerWrapper(new ThreadPoolTaskScheduler()))
.run(context -> {
ReactiveSessionManager bean = context.getBean(ReactiveSessionManager.class);
assertThat(bean).isExactlyInstanceOf(CachingVaultTokenSupplier.class);
});
}
@Test
public void shouldConfigureSessionManagement() {
this.contextRunner
.withPropertyValues("spring.cloud.vault.kv.enabled=false", "spring.cloud.vault.token=foo",
"spring.cloud.vault.session.lifecycle.refresh-before-expiry=11s",
"spring.cloud.vault.session.lifecycle.expiry-threshold=12s")
.withBean("vaultTokenSupplier", VaultTokenSupplier.class, () -> Mono::empty)
.withBean("taskSchedulerWrapper", VaultAutoConfiguration.TaskSchedulerWrapper.class,
() -> new VaultAutoConfiguration.TaskSchedulerWrapper(new ThreadPoolTaskScheduler()))
.run(context -> {
ReactiveSessionManager bean = context.getBean(ReactiveSessionManager.class);
Object refreshTrigger = ReflectionTestUtils.getField(bean, "refreshTrigger");
assertThat(refreshTrigger).hasFieldOrPropertyWithValue("duration", Duration.ofSeconds(11))
.hasFieldOrPropertyWithValue("expiryThreshold", Duration.ofSeconds(12));
});
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationFactoryConfiguration {
@Bean
AuthenticationStepsFactory authenticationStepsFactory() {
return () -> AuthenticationSteps.just(VaultToken.of("foo"));
}
}
@Configuration(proxyBeanMethods = false)
static class TokenSupplierConfiguration {
@Bean
VaultTokenSupplier vaultTokenSupplier() {
AtomicLong counter = new AtomicLong();
return () -> Mono.just(VaultToken.of("token-" + counter.incrementAndGet()));
}
}
@Configuration
static class CustomSessionManager {
@Bean
ReactiveSessionManager reactiveVaultSessionManager(VaultTokenSupplier tokenSupplier) {
return tokenSupplier::getVaultToken;
}
}
}

View File

@@ -39,6 +39,7 @@ import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.reactive.function.client.WebClient;
@@ -63,8 +64,8 @@ public class VaultReactiveBootstrapConfigurationTests {
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull();
assertThat(context.getBean(AuthenticationStepsFactory.class)).isNotNull();
assertThat(context).hasSingleBean(ReactiveVaultOperations.class);
assertThat(context).hasSingleBean(AuthenticationStepsFactory.class);
assertThat(context.getBean(SessionManager.class)).isNotNull()
.isNotInstanceOf(LifecycleAwareSessionManager.class)
.isNotInstanceOf(SimpleSessionManager.class);
@@ -79,7 +80,7 @@ public class VaultReactiveBootstrapConfigurationTests {
this.contextRunner.withUserConfiguration(AuthenticationFactoryConfiguration.class)
.withClassLoader(new FilteredClassLoader("reactor.netty.http.client.HttpClient")).run(context -> {
assertThat(context.getBeanNamesForType(ReactiveVaultOperations.class)).isEmpty();
assertThat(context).doesNotHaveBean(ReactiveVaultOperations.class);
});
}
@@ -91,11 +92,11 @@ public class VaultReactiveBootstrapConfigurationTests {
"spring.cloud.bootstrap.enabled=true")
.run(context -> {
assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull();
assertThat(context).hasSingleBean(ReactiveVaultOperations.class);
assertThat(context.getBean(SessionManager.class)).isNotNull()
.isNotInstanceOf(LifecycleAwareSessionManager.class)
.isNotInstanceOf(SimpleSessionManager.class);
assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty();
assertThat(context).doesNotHaveBean(WebClient.class);
});
}
@@ -106,7 +107,8 @@ public class VaultReactiveBootstrapConfigurationTests {
.withPropertyValues("spring.cloud.vault.reactive.enabled=false", "spring.cloud.vault.token=foo")
.run(context -> {
assertThat(context.getBeanNamesForType(ReactiveVaultOperations.class)).isEmpty();
assertThat(context).doesNotHaveBean(ReactiveVaultTemplate.class)
.doesNotHaveBean(ReactiveVaultOperations.class);
assertThat(context.getBean(SessionManager.class)).isInstanceOf(LifecycleAwareSessionManager.class);
});
}