Extract bean instance creation into VaultConfiguration and VaultReactiveAutoConfiguration
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -47,7 +47,7 @@ class PropertySourceLocatorConfigurationFactory {
|
||||
* @return the {@link PropertySourceLocatorConfiguration}.
|
||||
*/
|
||||
PropertySourceLocatorConfiguration getPropertySourceConfiguration(
|
||||
List<VaultKeyValueBackendPropertiesSupport> keyValueBackends) {
|
||||
VaultKeyValueBackendPropertiesSupport... keyValueBackends) {
|
||||
|
||||
DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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=\
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user