Add Vault implementation for ConfigDataResolver and ConfigDataLoader
Discover SecretBackendMetadataFactory and VaultSecretBackendDescriptor using spring.factories. Add instance suppliers for all imperative support classes. Resolves gh-483.
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
# Bootstrap Configuration
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.vault.config.aws.VaultConfigAwsBootstrapConfiguration
|
||||
org.springframework.cloud.vault.config.aws.VaultConfigAwsBootstrapConfiguration
|
||||
|
||||
org.springframework.cloud.vault.config.SecretBackendMetadataFactory=\
|
||||
org.springframework.cloud.vault.config.aws.VaultConfigAwsBootstrapConfiguration.AwsSecretBackendMetadataFactory
|
||||
|
||||
org.springframework.cloud.vault.config.VaultSecretBackendDescriptor=\
|
||||
org.springframework.cloud.vault.config.aws.VaultAwsProperties
|
||||
|
||||
@@ -3,4 +3,7 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.vault.config.consul.VaultConfigConsulBootstrapConfiguration
|
||||
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.vault.config.consul.VaultConfigConsulAutoConfiguration
|
||||
org.springframework.cloud.vault.config.consul.VaultConfigConsulAutoConfiguration
|
||||
|
||||
org.springframework.cloud.vault.config.VaultSecretBackendDescriptor=\
|
||||
org.springframework.cloud.vault.config.consul.VaultConsulProperties
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
# Bootstrap Configuration
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration
|
||||
org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration
|
||||
|
||||
org.springframework.cloud.vault.config.SecretBackendMetadataFactory=\
|
||||
org.springframework.cloud.vault.config.databases.VaultConfigDatabaseBootstrapConfiguration.DatabaseSecretBackendMetadataFactory
|
||||
|
||||
org.springframework.cloud.vault.config.VaultSecretBackendDescriptor=\
|
||||
org.springframework.cloud.vault.config.databases.VaultMySqlProperties,\
|
||||
org.springframework.cloud.vault.config.databases.VaultPostgreSqlProperties,\
|
||||
org.springframework.cloud.vault.config.databases.VaultCassandraProperties,\
|
||||
org.springframework.cloud.vault.config.databases.VaultMongoProperties,\
|
||||
org.springframework.cloud.vault.config.databases.VaultElasticsearchProperties,\
|
||||
org.springframework.cloud.vault.config.databases.VaultDatabaseProperties
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# Bootstrap Configuration
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration
|
||||
org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration
|
||||
|
||||
org.springframework.cloud.vault.config.SecretBackendMetadataFactory=\
|
||||
org.springframework.cloud.vault.config.rabbitmq.VaultConfigRabbitMqBootstrapConfiguration.RabbitMqSecretBackendMetadataFactory
|
||||
|
||||
org.springframework.cloud.vault.config.VaultSecretBackendDescriptor=\
|
||||
org.springframework.cloud.vault.config.rabbitmq.VaultRabbitMqProperties
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Factory for {@link PropertySourceLocatorConfigurationFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.0
|
||||
*/
|
||||
class PropertySourceLocatorConfigurationFactory {
|
||||
|
||||
private final Collection<VaultConfigurer> configurers;
|
||||
|
||||
private final Collection<VaultSecretBackendDescriptor> vaultSecretBackendDescriptors;
|
||||
|
||||
private final Collection<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> factories;
|
||||
|
||||
PropertySourceLocatorConfigurationFactory(Collection<VaultConfigurer> configurers,
|
||||
Collection<VaultSecretBackendDescriptor> vaultSecretBackendDescriptors,
|
||||
Collection<SecretBackendMetadataFactory<? super VaultSecretBackendDescriptor>> factories) {
|
||||
this.configurers = configurers;
|
||||
this.vaultSecretBackendDescriptors = vaultSecretBackendDescriptors;
|
||||
this.factories = factories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply configuration through {@link VaultConfigurer}.
|
||||
* @param keyValueBackends configured backend.
|
||||
* @return the {@link PropertySourceLocatorConfiguration}.
|
||||
*/
|
||||
PropertySourceLocatorConfiguration getPropertySourceConfiguration(
|
||||
List<VaultKeyValueBackendPropertiesSupport> keyValueBackends) {
|
||||
|
||||
DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer();
|
||||
|
||||
if (this.configurers.isEmpty()) {
|
||||
secretBackendConfigurer.registerDefaultKeyValueSecretBackends(true)
|
||||
.registerDefaultDiscoveredSecretBackends(true);
|
||||
}
|
||||
else {
|
||||
|
||||
for (VaultConfigurer vaultConfigurer : this.configurers) {
|
||||
vaultConfigurer.addSecretBackends(secretBackendConfigurer);
|
||||
}
|
||||
}
|
||||
|
||||
if (secretBackendConfigurer.isRegisterDefaultKeyValueSecretBackends()) {
|
||||
|
||||
for (VaultKeyValueBackendPropertiesSupport keyValueBackend : keyValueBackends) {
|
||||
|
||||
if (!keyValueBackend.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> contexts = KeyValueSecretBackendMetadata.buildContexts(keyValueBackend,
|
||||
keyValueBackend.getProfiles());
|
||||
|
||||
for (String context : contexts) {
|
||||
secretBackendConfigurer
|
||||
.add(KeyValueSecretBackendMetadata.create(keyValueBackend.getBackend(), context));
|
||||
}
|
||||
}
|
||||
|
||||
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
|
||||
.createSecretBackendMetadata(this.vaultSecretBackendDescriptors, this.factories);
|
||||
|
||||
backendAccessors.forEach(secretBackendConfigurer::add);
|
||||
}
|
||||
|
||||
if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) {
|
||||
|
||||
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
|
||||
.createSecretBackendMetadata(this.vaultSecretBackendDescriptors, this.factories);
|
||||
|
||||
backendAccessors.forEach(secretBackendConfigurer::add);
|
||||
}
|
||||
|
||||
return secretBackendConfigurer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package org.springframework.cloud.vault.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
@@ -26,10 +25,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.cloud.vault.config.VaultBootstrapConfiguration.TaskSchedulerWrapper;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -38,6 +35,8 @@ import org.springframework.vault.authentication.SessionManager;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
|
||||
import static org.springframework.cloud.vault.config.VaultAutoConfiguration.TaskSchedulerWrapper;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.cloud.bootstrap.BootstrapConfiguration Auto-configuration}
|
||||
* for Spring Vault's {@link PropertySourceLocator} support.
|
||||
@@ -47,7 +46,6 @@ import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
* @author Mårten Svantesson
|
||||
* @since 1.1
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(VaultKeyValueBackendProperties.class)
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 10)
|
||||
@@ -81,8 +79,13 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
|
||||
|
||||
VaultConfigTemplate vaultConfigTemplate = new VaultConfigTemplate(operations, vaultProperties);
|
||||
|
||||
PropertySourceLocatorConfiguration configuration = getPropertySourceConfiguration(
|
||||
Collections.singletonList(kvBackendProperties));
|
||||
Collection<VaultConfigurer> vaultConfigurers = this.applicationContext.getBeansOfType(VaultConfigurer.class)
|
||||
.values();
|
||||
PropertySourceLocatorConfigurationFactory factory = new PropertySourceLocatorConfigurationFactory(
|
||||
vaultConfigurers, this.vaultSecretBackendDescriptors, this.factories);
|
||||
|
||||
PropertySourceLocatorConfiguration configuration = factory
|
||||
.getPropertySourceConfiguration(Collections.singletonList(kvBackendProperties));
|
||||
|
||||
VaultProperties.ConfigLifecycle lifecycle = vaultProperties.getConfig().getLifecycle();
|
||||
|
||||
@@ -102,64 +105,6 @@ public class VaultBootstrapPropertySourceConfiguration implements InitializingBe
|
||||
return new VaultPropertySourceLocator(vaultConfigTemplate, vaultProperties, configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply configuration through {@link VaultConfigurer}.
|
||||
* @param keyValueBackends configured backend.
|
||||
* @return the {@link PropertySourceLocatorConfiguration}.
|
||||
*/
|
||||
private PropertySourceLocatorConfiguration getPropertySourceConfiguration(
|
||||
List<VaultKeyValueBackendPropertiesSupport> keyValueBackends) {
|
||||
|
||||
Collection<VaultConfigurer> configurers = this.applicationContext.getBeansOfType(VaultConfigurer.class)
|
||||
.values();
|
||||
|
||||
DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer();
|
||||
|
||||
if (configurers.isEmpty()) {
|
||||
secretBackendConfigurer.registerDefaultKeyValueSecretBackends(true)
|
||||
.registerDefaultDiscoveredSecretBackends(true);
|
||||
}
|
||||
else {
|
||||
|
||||
for (VaultConfigurer vaultConfigurer : configurers) {
|
||||
vaultConfigurer.addSecretBackends(secretBackendConfigurer);
|
||||
}
|
||||
}
|
||||
|
||||
if (secretBackendConfigurer.isRegisterDefaultKeyValueSecretBackends()) {
|
||||
|
||||
for (VaultKeyValueBackendPropertiesSupport keyValueBackend : keyValueBackends) {
|
||||
|
||||
if (!keyValueBackend.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> contexts = KeyValueSecretBackendMetadata.buildContexts(keyValueBackend,
|
||||
keyValueBackend.getProfiles());
|
||||
|
||||
for (String context : contexts) {
|
||||
secretBackendConfigurer
|
||||
.add(KeyValueSecretBackendMetadata.create(keyValueBackend.getBackend(), context));
|
||||
}
|
||||
}
|
||||
|
||||
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
|
||||
.createSecretBackendMetadata(this.vaultSecretBackendDescriptors, this.factories);
|
||||
|
||||
backendAccessors.forEach(secretBackendConfigurer::add);
|
||||
}
|
||||
|
||||
if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) {
|
||||
|
||||
Collection<SecretBackendMetadata> backendAccessors = SecretBackendFactories
|
||||
.createSecretBackendMetadata(this.vaultSecretBackendDescriptors, this.factories);
|
||||
|
||||
backendAccessors.forEach(secretBackendConfigurer::add);
|
||||
}
|
||||
|
||||
return secretBackendConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vaultProperties the {@link VaultProperties}.
|
||||
* @param vaultOperations the {@link VaultOperations}.
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
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.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.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.VaultException;
|
||||
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.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.core.VaultTemplate;
|
||||
import org.springframework.vault.core.env.LeaseAwareVaultPropertySource;
|
||||
import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
import org.springframework.vault.core.lease.domain.RequestedSecret;
|
||||
import org.springframework.vault.core.lease.event.LeaseErrorListener;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
|
||||
|
||||
/**
|
||||
* {@link ConfigDataLoader} for Vault for {@link VaultConfigLocation}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.0
|
||||
*/
|
||||
public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocation> {
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
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));
|
||||
|
||||
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)));
|
||||
}
|
||||
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)));
|
||||
}
|
||||
|
||||
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
|
||||
|
||||
VaultProperties.ConfigLifecycle lifecycle = vaultProperties.getConfig().getLifecycle();
|
||||
|
||||
bootstrap.registerIfAbsent(SecretLeaseContainer.class, ctx -> {
|
||||
SecretLeaseContainer container = new SecretLeaseContainer(ctx.get(VaultTemplate.class),
|
||||
ctx.get(TaskSchedulerWrapper.class).getTaskScheduler());
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
RequestedSecret secret = getRequestedSecret(location.getSecretBackendMetadata());
|
||||
|
||||
if (vaultProperties.isFailFast()) {
|
||||
return new ConfigData(Collections.singleton(createLeasingPropertySourceFailFast(
|
||||
bootstrap.get(SecretLeaseContainer.class), secret, location.getSecretBackendMetadata())));
|
||||
}
|
||||
|
||||
return new ConfigData(Collections.singleton(createLeasingPropertySource(
|
||||
bootstrap.get(SecretLeaseContainer.class), secret, location.getSecretBackendMetadata())));
|
||||
}
|
||||
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 PropertySource<?> createVaultPropertySource(VaultConfigOperations configOperations, boolean failFast,
|
||||
SecretBackendMetadata accessor) {
|
||||
|
||||
VaultPropertySource vaultPropertySource = new VaultPropertySource(configOperations, failFast, accessor);
|
||||
vaultPropertySource.init();
|
||||
return vaultPropertySource;
|
||||
}
|
||||
|
||||
private PropertySource<?> createLeasingPropertySource(SecretLeaseContainer secretLeaseContainer,
|
||||
RequestedSecret secret, SecretBackendMetadata accessor) {
|
||||
|
||||
if (accessor instanceof LeasingSecretBackendMetadata) {
|
||||
((LeasingSecretBackendMetadata) accessor).beforeRegistration(secret, secretLeaseContainer);
|
||||
}
|
||||
|
||||
LeaseAwareVaultPropertySource propertySource = new LeaseAwareVaultPropertySource(accessor.getName(),
|
||||
secretLeaseContainer, secret, accessor.getPropertyTransformer());
|
||||
|
||||
if (accessor instanceof LeasingSecretBackendMetadata) {
|
||||
((LeasingSecretBackendMetadata) accessor).afterRegistration(secret, secretLeaseContainer);
|
||||
}
|
||||
|
||||
return propertySource;
|
||||
}
|
||||
|
||||
private PropertySource<?> createLeasingPropertySourceFailFast(SecretLeaseContainer secretLeaseContainer,
|
||||
RequestedSecret secret, SecretBackendMetadata accessor) {
|
||||
|
||||
final AtomicReference<Exception> errorRef = new AtomicReference<>();
|
||||
|
||||
LeaseErrorListener errorListener = (leaseEvent, exception) -> {
|
||||
|
||||
if (leaseEvent.getSource() == secret) {
|
||||
errorRef.compareAndSet(null, exception);
|
||||
}
|
||||
};
|
||||
|
||||
secretLeaseContainer.addErrorListener(errorListener);
|
||||
try {
|
||||
return createLeasingPropertySource(secretLeaseContainer, secret, accessor);
|
||||
}
|
||||
finally {
|
||||
secretLeaseContainer.removeLeaseErrorListener(errorListener);
|
||||
|
||||
Exception exception = errorRef.get();
|
||||
if (exception != null) {
|
||||
if (exception instanceof VaultException) {
|
||||
throw (VaultException) exception;
|
||||
}
|
||||
throw new VaultException(
|
||||
String.format("Cannot initialize PropertySource for secret at %s", secret.getPath()),
|
||||
exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RequestedSecret getRequestedSecret(SecretBackendMetadata accessor) {
|
||||
|
||||
if (accessor instanceof LeasingSecretBackendMetadata) {
|
||||
|
||||
LeasingSecretBackendMetadata leasingBackend = (LeasingSecretBackendMetadata) accessor;
|
||||
return RequestedSecret.from(leasingBackend.getLeaseMode(), accessor.getPath());
|
||||
}
|
||||
|
||||
if (accessor instanceof KeyValueSecretBackendMetadata) {
|
||||
return RequestedSecret.rotating(accessor.getPath());
|
||||
}
|
||||
|
||||
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 class ImperativeConfiguration {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
private final VaultEndpointProvider endpointProvider;
|
||||
|
||||
ImperativeConfiguration(VaultProperties vaultProperties) {
|
||||
this.vaultProperties = vaultProperties;
|
||||
this.endpointProvider = SimpleVaultEndpointProvider
|
||||
.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties));
|
||||
}
|
||||
|
||||
public RestTemplateBuilder createRestTemplateBuilder(ClientHttpRequestFactory requestFactory) {
|
||||
|
||||
RestTemplateBuilder builder = RestTemplateBuilder.builder().requestFactory(requestFactory)
|
||||
.endpointProvider(this.endpointProvider);
|
||||
|
||||
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
|
||||
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO
|
||||
static class ReactiveConfiguration {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
private final VaultEndpointProvider endpointProvider;
|
||||
|
||||
ReactiveConfiguration(VaultProperties vaultProperties) {
|
||||
this.vaultProperties = vaultProperties;
|
||||
this.endpointProvider = SimpleVaultEndpointProvider
|
||||
.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties));
|
||||
}
|
||||
|
||||
public WebClientBuilder createRestTemplateBuilder(ClientHttpConnector connector) {
|
||||
|
||||
WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector)
|
||||
.endpointProvider(this.endpointProvider);
|
||||
|
||||
if (StringUtils.hasText(this.vaultProperties.getNamespace())) {
|
||||
builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolver;
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
import org.springframework.boot.context.config.Profiles;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ConfigDataLocationResolver} for Vault resolving {@link VaultConfigLocation}
|
||||
* using the {@code vault:} prefix.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultConfigDataLocationResolver implements ConfigDataLocationResolver<VaultConfigLocation> {
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
|
||||
|
||||
boolean vaultEnabled = context.getBinder().bind(VaultProperties.PREFIX + ".enabled", Boolean.class)
|
||||
.orElse(true);
|
||||
|
||||
return location.startsWith(VaultConfigLocation.VAULT_PREFIX) && vaultEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VaultConfigLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) throws ConfigDataLocationNotFoundException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
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.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);
|
||||
|
||||
return sorted.stream().map(it -> new VaultConfigLocation(it, optional)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
String contextPath = location.substring(VaultConfigLocation.VAULT_PREFIX.length());
|
||||
|
||||
return Collections.singletonList(new VaultConfigLocation(contextPath, optional));
|
||||
}
|
||||
|
||||
private static String getApplicationName(Binder binder) {
|
||||
|
||||
return binder.bind("spring.cloud.vault.application-name", String.class)
|
||||
.orElseGet(() -> binder.bind("spring.application-name", String.class).orElse(""));
|
||||
}
|
||||
|
||||
private List<VaultSecretBackendDescriptor> findDescriptors(ConfigDataLocationResolverContext context) {
|
||||
|
||||
List<String> descriptorClasses = SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptor.class,
|
||||
getClass().getClassLoader());
|
||||
|
||||
List<VaultSecretBackendDescriptor> descriptors = new ArrayList<>(descriptorClasses.size());
|
||||
|
||||
try {
|
||||
for (String className : descriptorClasses) {
|
||||
|
||||
Class<VaultSecretBackendDescriptor> descriptorClass = (Class<VaultSecretBackendDescriptor>) ClassUtils
|
||||
.forName(className, getClass().getClassLoader());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
ReflectionUtils.rethrowRuntimeException(e);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.context.config.ConfigDataLocation;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultConfigLocation extends ConfigDataLocation {
|
||||
|
||||
/**
|
||||
* Prefix used to indicate a {@link VaultConfigLocation}.
|
||||
*/
|
||||
public static final String VAULT_PREFIX = "vault:";
|
||||
|
||||
private final SecretBackendMetadata secretBackendMetadata;
|
||||
|
||||
private final boolean optional;
|
||||
|
||||
public VaultConfigLocation(String contextPath, boolean optional) {
|
||||
this.secretBackendMetadata = KeyValueSecretBackendMetadata.create(contextPath);
|
||||
this.optional = optional;
|
||||
}
|
||||
|
||||
public VaultConfigLocation(SecretBackendMetadata secretBackendMetadata, boolean optional) {
|
||||
this.secretBackendMetadata = secretBackendMetadata;
|
||||
this.optional = optional;
|
||||
}
|
||||
|
||||
public SecretBackendMetadata getSecretBackendMetadata() {
|
||||
return this.secretBackendMetadata;
|
||||
}
|
||||
|
||||
public boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof VaultConfigLocation)) {
|
||||
return false;
|
||||
}
|
||||
VaultConfigLocation that = (VaultConfigLocation) o;
|
||||
if (this.optional != that.optional) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.secretBackendMetadata.getName(), that.secretBackendMetadata.getName())
|
||||
&& ObjectUtils.nullSafeEquals(this.secretBackendMetadata.getPath(),
|
||||
that.secretBackendMetadata.getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(this.secretBackendMetadata.getName());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.secretBackendMetadata.getPath());
|
||||
result = 31 * result + (this.optional ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
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(", optional=").append(this.optional);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,10 +17,14 @@
|
||||
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;
|
||||
|
||||
@@ -36,6 +40,16 @@ final class 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.
|
||||
|
||||
@@ -36,10 +36,15 @@ import org.springframework.validation.annotation.Validated;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.vault.kv")
|
||||
@ConfigurationProperties(VaultKeyValueBackendProperties.PREFIX)
|
||||
@Validated
|
||||
public class VaultKeyValueBackendProperties implements EnvironmentAware, VaultKeyValueBackendPropertiesSupport {
|
||||
|
||||
/**
|
||||
* Configuration prefix for config properties.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.vault.kv";
|
||||
|
||||
/**
|
||||
* Enable the kev-value backend.
|
||||
*/
|
||||
|
||||
@@ -40,10 +40,15 @@ import org.springframework.vault.core.lease.LeaseEndpoints;
|
||||
* @author Grenville Wilson
|
||||
* @author Mårten Svantesson
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.vault")
|
||||
@ConfigurationProperties(VaultProperties.PREFIX)
|
||||
@Validated
|
||||
public class VaultProperties implements EnvironmentAware {
|
||||
|
||||
/**
|
||||
* Configuration prefix for config properties.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.vault";
|
||||
|
||||
/**
|
||||
* Enable Vault config server.
|
||||
*/
|
||||
|
||||
@@ -7,3 +7,10 @@ org.springframework.cloud.vault.config.DiscoveryClientVaultBootstrapConfiguratio
|
||||
org.springframework.cloud.vault.config.VaultBootstrapConfiguration,\
|
||||
org.springframework.cloud.vault.config.VaultReactiveBootstrapConfiguration,\
|
||||
org.springframework.cloud.vault.config.VaultBootstrapPropertySourceConfiguration
|
||||
# ConfigData Resolver
|
||||
org.springframework.boot.context.config.ConfigDataLocationResolver=\
|
||||
org.springframework.cloud.vault.config.VaultConfigDataLocationResolver
|
||||
# ConfigData Loader
|
||||
org.springframework.boot.context.config.ConfigDataLoader=\
|
||||
org.springframework.cloud.vault.config.VaultConfigDataLoader
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration test using Spring Boot's ConfigData API with token authentication.
|
||||
*
|
||||
* <p>
|
||||
* In case this test should fail because of SSL make sure you run the test within the
|
||||
* spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is
|
||||
* referenced with {@code ../work/keystore.jks}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = VaultConfigLoaderSingleLocationTests.TestApplication.class, properties = {
|
||||
"spring.cloud.vault.uri=https://localhost:8200", "spring.config.import=vault:secret/config-location" })
|
||||
public class VaultConfigLoaderSingleLocationTests {
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
Map<String, Object> object = new HashMap<>();
|
||||
object.put("vault-key", "config-data works");
|
||||
object.put("nested", Collections.singletonMap("key", "value"));
|
||||
|
||||
vaultRule.prepare().getVaultOperations().write("secret/config-location", object);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldContainProperty() {
|
||||
|
||||
assertThat(this.environment.containsProperty("vault-key")).isTrue();
|
||||
assertThat(this.environment.getProperty("vault-key")).isEqualTo("config-data works");
|
||||
}
|
||||
|
||||
@SpringBootApplication(exclude = RefreshAutoConfiguration.class)
|
||||
public static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration test using Spring Boot's ConfigData API with token authentication.
|
||||
*
|
||||
* <p>
|
||||
* In case this test should fail because of SSL make sure you run the test within the
|
||||
* spring-cloud-vault-config/spring-cloud-vault-config directory as the keystore is
|
||||
* referenced with {@code ../work/keystore.jks}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(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" })
|
||||
public class VaultConfigLoaderTests {
|
||||
|
||||
@Value("${vault.value}")
|
||||
String configValue;
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
Map<String, Object> object = new HashMap<>();
|
||||
object.put("vault.value", "config-data works");
|
||||
object.put("nested", Collections.singletonMap("key", "value"));
|
||||
|
||||
vaultRule.prepare().getVaultOperations().write("secret/config-data", object);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
assertThat(this.configValue).isEqualTo("config-data works");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldContainProperty() {
|
||||
|
||||
assertThat(this.environment.containsProperty("vault.value")).isTrue();
|
||||
assertThat(this.environment.getProperty("vault.value")).isEqualTo("config-data works");
|
||||
|
||||
assertThat(this.environment.containsProperty("nested.key")).isTrue();
|
||||
assertThat(this.environment.getProperty("nested.key")).isEqualTo("value");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void shouldContainVaultBeans() {
|
||||
|
||||
assertThat(this.applicationContext.getBeanNamesForType(VaultTemplate.class)).isNotEmpty();
|
||||
assertThat(this.applicationContext.getBeanNamesForType(LeasingVaultPropertySourceLocator.class)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotContainRestTemplateArtifacts() {
|
||||
|
||||
assertThat(this.applicationContext.getBeanNamesForType(RestTemplate.class)).isEmpty();
|
||||
assertThat(this.applicationContext.getBeanNamesForType(ClientHttpRequestFactory.class)).isEmpty();
|
||||
}
|
||||
|
||||
@SpringBootApplication(exclude = RefreshAutoConfiguration.class)
|
||||
public static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
application.name: testVaultApp
|
||||
cloud.vault.token: 00000000-0000-0000-0000-000000000000
|
||||
cloud.vault.ssl.trust-store: file:../work/keystore.jks
|
||||
cloud.vault.ssl.trust-store-password: changeit
|
||||
Reference in New Issue
Block a user