diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java index e2f64152..a53e2827 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.AnnotationAwareOrderComparator; @@ -38,19 +39,15 @@ import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.util.StringUtils; import org.springframework.vault.authentication.ClientAuthentication; import org.springframework.vault.authentication.LifecycleAwareSessionManager; -import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport; import org.springframework.vault.authentication.SessionManager; -import org.springframework.vault.authentication.SimpleSessionManager; 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.SimpleVaultEndpointProvider; import org.springframework.vault.client.VaultEndpointProvider; -import org.springframework.vault.client.VaultHttpHeaders; import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper; import org.springframework.vault.core.VaultOperations; import org.springframework.vault.core.VaultTemplate; @@ -63,39 +60,26 @@ import org.springframework.web.client.RestTemplate; * * @author Spencer Gibb * @author Mark Paluch + * @since 3.0 */ +@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) @EnableConfigurationProperties(VaultProperties.class) @Order(Ordered.LOWEST_PRECEDENCE - 5) -public class VaultAutoConfiguration implements InitializingBean { +public class VaultAutoConfiguration { private final ConfigurableApplicationContext applicationContext; private final VaultProperties vaultProperties; + private final VaultConfiguration configuration; + private final VaultEndpointProvider endpointProvider; private final List customizers; private final List> requestCustomizers; - private ClientFactoryWrapper clientFactoryWrapper; - - /** - * Used for Vault communication. - */ - private RestTemplateBuilder restTemplateBuilder; - - /** - * Used for Vault communication. - */ - private RestTemplateFactory restTemplateFactory; - - /** - * Used for external (AWS, GCP) communication. - */ - private RestTemplate externalRestOperations; - public VaultAutoConfiguration(ConfigurableApplicationContext applicationContext, VaultProperties vaultProperties, ObjectProvider endpointProvider, ObjectProvider> customizers, @@ -103,11 +87,12 @@ public class VaultAutoConfiguration implements InitializingBean { this.applicationContext = applicationContext; this.vaultProperties = vaultProperties; + this.configuration = new VaultConfiguration(vaultProperties); VaultEndpointProvider provider = endpointProvider.getIfAvailable(); if (provider == null) { - provider = SimpleVaultEndpointProvider.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties)); + provider = SimpleVaultEndpointProvider.of(this.configuration.createVaultEndpoint()); } this.endpointProvider = provider; @@ -118,41 +103,18 @@ public class VaultAutoConfiguration implements InitializingBean { AnnotationAwareOrderComparator.sort(this.requestCustomizers); } - @Override - public void afterPropertiesSet() { - - this.clientFactoryWrapper = createClientFactoryWrapper(); - - this.restTemplateBuilder = restTemplateBuilder(this.clientFactoryWrapper.getClientHttpRequestFactory()); - - this.externalRestOperations = new RestTemplate(this.clientFactoryWrapper.getClientHttpRequestFactory()); - - this.restTemplateFactory = new DefaultRestTemplateFactory( - this.clientFactoryWrapper.getClientHttpRequestFactory(), this::restTemplateBuilder); - - this.customizers.forEach(customizer -> customizer.customize(this.externalRestOperations)); - } - /** * Create a {@link RestTemplateBuilder} initialized with {@link VaultEndpointProvider} * and {@link ClientHttpRequestFactory}. May be overridden by subclasses. + * @param requestFactory the {@link ClientHttpRequestFactory}. * @return the {@link RestTemplateBuilder}. * @since 2.3 * @see #clientHttpRequestFactoryWrapper() */ protected RestTemplateBuilder restTemplateBuilder(ClientHttpRequestFactory requestFactory) { - RestTemplateBuilder builder = RestTemplateBuilder.builder().requestFactory(requestFactory) - .endpointProvider(this.endpointProvider); - - this.customizers.forEach(builder::customizers); - this.requestCustomizers.forEach(builder::requestCustomizers); - - if (StringUtils.hasText(this.vaultProperties.getNamespace())) { - builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace()); - } - - return builder; + return this.configuration.createRestTemplateBuilder(requestFactory, this.endpointProvider, this.customizers, + this.requestCustomizers); } /** @@ -167,37 +129,43 @@ public class VaultAutoConfiguration implements InitializingBean { @Bean @ConditionalOnMissingBean public ClientFactoryWrapper clientHttpRequestFactoryWrapper() { - return this.clientFactoryWrapper; + return new ClientFactoryWrapper(this.configuration.createClientHttpRequestFactory()); } /** * Create a {@link RestTemplateFactory} bean that is used to produce * {@link RestTemplate}. + * @param clientFactoryWrapper the {@link ClientFactoryWrapper}. * @return the {@link RestTemplateFactory}. * @see #clientHttpRequestFactoryWrapper() * @since 3.0 */ @Bean - public RestTemplateFactory vaultRestTemplateFactory() { - return this.restTemplateFactory; + @ConditionalOnMissingBean + public RestTemplateFactory vaultRestTemplateFactory(ClientFactoryWrapper clientFactoryWrapper) { + return new DefaultRestTemplateFactory(clientFactoryWrapper.getClientHttpRequestFactory(), + this::restTemplateBuilder); } /** * Creates a {@link VaultTemplate}. + * @param clientFactoryWrapper the {@link ClientFactoryWrapper}. * @return the {@link VaultTemplate} bean. * @see VaultAutoConfiguration#clientHttpRequestFactoryWrapper() */ @Bean @ConditionalOnMissingBean(VaultOperations.class) - public VaultTemplate vaultTemplate() { + public VaultTemplate vaultTemplate(ClientFactoryWrapper clientFactoryWrapper) { VaultProperties.AuthenticationMethod authentication = this.vaultProperties.getAuthentication(); + RestTemplateBuilder restTemplateBuilder = restTemplateBuilder( + clientFactoryWrapper.getClientHttpRequestFactory()); if (authentication == VaultProperties.AuthenticationMethod.NONE) { - return new VaultTemplate(this.restTemplateBuilder); + return new VaultTemplate(restTemplateBuilder); } - return new VaultTemplate(this.restTemplateBuilder, this.applicationContext.getBean(SessionManager.class)); + return new VaultTemplate(restTemplateBuilder, this.applicationContext.getBean(SessionManager.class)); } /** @@ -211,10 +179,7 @@ public class VaultAutoConfiguration implements InitializingBean { @ConditionalOnMissingBean(TaskSchedulerWrapper.class) public TaskSchedulerWrapper vaultTaskScheduler() { - ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); - threadPoolTaskScheduler.setPoolSize(2); - threadPoolTaskScheduler.setDaemon(true); - threadPoolTaskScheduler.setThreadNamePrefix("Spring-Cloud-Vault-"); + ThreadPoolTaskScheduler threadPoolTaskScheduler = VaultConfiguration.createScheduler(); // This is to destroy bootstrap resources // otherwise, the bootstrap context is not shut down cleanly @@ -228,6 +193,7 @@ public class VaultAutoConfiguration implements InitializingBean { * @param clientAuthentication the {@link ClientAuthentication}. * @param asyncTaskExecutorFactory the {@link ObjectFactory} for * {@link TaskSchedulerWrapper}. + * @param restTemplateFactory the {@link RestTemplateFactory}. * @see SessionManager * @see LifecycleAwareSessionManager */ @@ -235,43 +201,37 @@ public class VaultAutoConfiguration implements InitializingBean { @ConditionalOnMissingBean @ConditionalOnAuthentication public SessionManager vaultSessionManager(ClientAuthentication clientAuthentication, - ObjectFactory asyncTaskExecutorFactory) { + ObjectFactory asyncTaskExecutorFactory, RestTemplateFactory restTemplateFactory) { - VaultProperties.SessionLifecycle lifecycle = this.vaultProperties.getSession().getLifecycle(); - - if (lifecycle.isEnabled()) { - RestTemplate restTemplate = this.restTemplateFactory.create(); - LifecycleAwareSessionManagerSupport.RefreshTrigger trigger = new LifecycleAwareSessionManagerSupport.FixedTimeoutRefreshTrigger( - lifecycle.getRefreshBeforeExpiry(), lifecycle.getExpiryThreshold()); - return new LifecycleAwareSessionManager(clientAuthentication, - asyncTaskExecutorFactory.getObject().getTaskScheduler(), restTemplate, trigger); - } - - return new SimpleSessionManager(clientAuthentication); + return this.configuration.createSessionManager(clientAuthentication, + () -> asyncTaskExecutorFactory.getObject().getTaskScheduler(), restTemplateFactory); } /** * @return the {@link ClientAuthentication} to obtain a * {@link org.springframework.vault.support.VaultToken}. + * @param clientFactoryWrapper the {@link ClientFactoryWrapper}. + * @param restTemplateFactory the {@link RestTemplateFactory}. * @see SessionManager * @see LifecycleAwareSessionManager */ @Bean @ConditionalOnMissingBean @ConditionalOnAuthentication - public ClientAuthentication clientAuthentication() { + public ClientAuthentication clientAuthentication(ClientFactoryWrapper clientFactoryWrapper, + RestTemplateFactory restTemplateFactory) { - RestTemplate restTemplate = this.restTemplateFactory.create(); + RestTemplate externalRestOperations = new RestTemplate(clientFactoryWrapper.getClientHttpRequestFactory()); + + this.customizers.forEach(customizer -> customizer.customize(externalRestOperations)); + + RestTemplate restTemplate = restTemplateFactory.create(); ClientAuthenticationFactory factory = new ClientAuthenticationFactory(this.vaultProperties, restTemplate, - this.externalRestOperations); + externalRestOperations); return factory.createClientAuthentication(); } - protected ClientFactoryWrapper createClientFactoryWrapper() { - return new ClientFactoryWrapper(VaultConfigurationUtil.createClientHttpRequestFactory(this.vaultProperties)); - } - /** * Wrapper to keep {@link TaskScheduler} local to Spring Cloud Vault. */ @@ -279,8 +239,15 @@ public class VaultAutoConfiguration implements InitializingBean { private final ThreadPoolTaskScheduler taskScheduler; + private final boolean acceptAfterPropertiesSet; + public TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler) { + this(taskScheduler, true); + } + + TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet) { this.taskScheduler = taskScheduler; + this.acceptAfterPropertiesSet = acceptAfterPropertiesSet; } ThreadPoolTaskScheduler getTaskScheduler() { @@ -288,13 +255,16 @@ public class VaultAutoConfiguration implements InitializingBean { } @Override - public void destroy() throws Exception { + public void destroy() { this.taskScheduler.destroy(); } @Override - public void afterPropertiesSet() throws Exception { - this.taskScheduler.afterPropertiesSet(); + public void afterPropertiesSet() { + + if (this.acceptAfterPropertiesSet) { + this.taskScheduler.afterPropertiesSet(); + } } } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java index b4721a76..9a04a549 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java @@ -19,9 +19,9 @@ package org.springframework.cloud.vault.config; import java.util.List; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.bootstrap.BootstrapConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -30,21 +30,23 @@ import org.springframework.vault.client.RestTemplateRequestCustomizer; import org.springframework.vault.client.VaultEndpointProvider; /** - * {@link EnableAutoConfiguration Auto-configuration} for Spring Vault support. + * {@link BootstrapConfiguration Boostrap configuration} for Spring Vault support. * * @author Spencer Gibb * @author Mark Paluch + * @deprecated since 3.0, use {@link VaultReactiveAutoConfiguration} through + * {@code @EnableAutoConfiguration}. */ @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) @EnableConfigurationProperties(VaultProperties.class) @Order(Ordered.LOWEST_PRECEDENCE - 5) +@Deprecated public class VaultBootstrapConfiguration extends VaultAutoConfiguration { public VaultBootstrapConfiguration(ConfigurableApplicationContext applicationContext, VaultProperties vaultProperties, ObjectProvider endpointProvider, ObjectProvider> customizers, ObjectProvider>> requestCustomizers) { - super(applicationContext, vaultProperties, endpointProvider, customizers, requestCustomizers); } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveAutoConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveAutoConfiguration.java new file mode 100644 index 00000000..80755535 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveAutoConfiguration.java @@ -0,0 +1,211 @@ +/* + * 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.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import reactor.core.publisher.Flux; +import reactor.netty.http.client.HttpClient; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.util.Assert; +import org.springframework.vault.authentication.AuthenticationStepsFactory; +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.VaultTokenSupplier; +import org.springframework.vault.client.SimpleVaultEndpointProvider; +import org.springframework.vault.client.VaultEndpointProvider; +import org.springframework.vault.client.WebClientBuilder; +import org.springframework.vault.client.WebClientCustomizer; +import org.springframework.vault.client.WebClientFactory; +import org.springframework.vault.core.ReactiveVaultOperations; +import org.springframework.vault.core.ReactiveVaultTemplate; +import org.springframework.vault.support.ClientOptions; +import org.springframework.vault.support.SslConfiguration; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.cloud.vault.config.VaultAutoConfiguration.TaskSchedulerWrapper; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for reactive Spring Vault support. + *

+ * This auto-configuration only supports static endpoints without + * {@link VaultEndpointProvider} support as endpoint providers could be potentially + * blocking implementations. + * + * @author Mark Paluch + * @since 3.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@ConditionalOnExpression("${spring.cloud.vault.reactive.enabled:true}") +@ConditionalOnClass({ Flux.class, WebClient.class, ReactiveVaultOperations.class, HttpClient.class }) +@EnableConfigurationProperties({ VaultProperties.class }) +@AutoConfigureBefore(VaultAutoConfiguration.class) +public class VaultReactiveAutoConfiguration implements InitializingBean { + + private final VaultProperties vaultProperties; + + private final VaultReactiveConfiguration configuration; + + private final VaultEndpointProvider endpointProvider; + + private final List customizers; + + private ClientHttpConnector clientHttpConnector; + + /** + * Used for Vault communication. + */ + private WebClientBuilder webClientBuilder; + + public VaultReactiveAutoConfiguration(VaultProperties vaultProperties, + ObjectProvider endpointProvider, + ObjectProvider> webClientCustomizers) { + + this.vaultProperties = vaultProperties; + this.configuration = new VaultReactiveConfiguration(vaultProperties); + + this.endpointProvider = endpointProvider.getIfAvailable( + () -> SimpleVaultEndpointProvider.of(new VaultConfiguration(vaultProperties).createVaultEndpoint())); + this.customizers = new ArrayList<>(webClientCustomizers.getIfAvailable(Collections::emptyList)); + AnnotationAwareOrderComparator.sort(this.customizers); + } + + @Override + public void afterPropertiesSet() { + + this.clientHttpConnector = createConnector(this.vaultProperties); + + this.webClientBuilder = webClientBuilder(this.clientHttpConnector); + } + + protected WebClientBuilder webClientBuilder(ClientHttpConnector connector) { + return this.configuration.createWebClientBuilder(connector, this.endpointProvider, this.customizers); + } + + /** + * Creates a {@link ClientHttpConnector} configured with {@link ClientOptions} and + * {@link SslConfiguration} which are not necessarily applicable for the whole + * application. + * @param vaultProperties the Vault properties. + * @return the {@link ClientHttpConnector}. + */ + protected ClientHttpConnector createConnector(VaultProperties vaultProperties) { + return new VaultReactiveConfiguration(vaultProperties).createClientHttpConnector(); + } + + /** + * Create a {@link WebClientFactory} bean that is used to produce {@link WebClient}. + * @return the {@link WebClientFactory}. + * @since 3.0 + */ + @Bean + @ConditionalOnMissingBean + public WebClientFactory vaultWebClientFactory() { + return new DefaultWebClientFactory(this.clientHttpConnector, this::webClientBuilder); + } + + /** + * Creates a {@link ReactiveVaultTemplate}. + * @param sessionManager object provider for {@link ReactiveSessionManager}. + * @return the {@link ReactiveVaultTemplate} bean. + * @see #reactiveVaultSessionManager(BeanFactory, ObjectFactory, WebClientFactory) + */ + @Bean + @ConditionalOnMissingBean(ReactiveVaultOperations.class) + public ReactiveVaultTemplate reactiveVaultTemplate(ObjectProvider sessionManager) { + + if (this.vaultProperties.getAuthentication() == VaultProperties.AuthenticationMethod.NONE) { + return new ReactiveVaultTemplate(this.webClientBuilder); + } + + return new ReactiveVaultTemplate(this.webClientBuilder, sessionManager.getObject()); + } + + /** + * @param beanFactory the {@link BeanFactory}. + * @param asyncTaskExecutorFactory the {@link ObjectFactory} for + * {@link TaskSchedulerWrapper}. + * @param webClientFactory the web client factory + * @return {@link ReactiveSessionManager} for reactive session use. + * @see ReactiveSessionManager + * @see ReactiveLifecycleAwareSessionManager + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnAuthentication + public ReactiveSessionManager reactiveVaultSessionManager(BeanFactory beanFactory, + ObjectFactory asyncTaskExecutorFactory, WebClientFactory webClientFactory) { + + VaultTokenSupplier vaultTokenSupplier = beanFactory.getBean("vaultTokenSupplier", VaultTokenSupplier.class); + + return this.configuration.createReactiveSessionManager(vaultTokenSupplier, + () -> asyncTaskExecutorFactory.getObject().getTaskScheduler(), webClientFactory); + } + + /** + * @param sessionManager the {@link ReactiveSessionManager}. + * @return {@link SessionManager} adapter wrapping {@link ReactiveSessionManager}. + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnAuthentication + public SessionManager vaultSessionManager(ReactiveSessionManager sessionManager) { + return this.configuration.createSessionManager(sessionManager); + } + + /** + * @param beanFactory the {@link BeanFactory}. + * @param webClientFactory the {@link WebClientFactory}. + * @return the {@link VaultTokenSupplier} for reactive Vault session management + * adapting {@link ClientAuthentication} that also implement + * {@link AuthenticationStepsFactory}. + * @see AuthenticationStepsFactory + */ + @Bean + @ConditionalOnMissingBean(name = "vaultTokenSupplier") + @ConditionalOnAuthentication + public VaultTokenSupplier vaultTokenSupplier(ListableBeanFactory beanFactory, WebClientFactory webClientFactory) { + + Assert.notNull(beanFactory, "BeanFactory must not be null"); + + return this.configuration.createVaultTokenSupplier(webClientFactory, + () -> beanFactory.getBeanProvider(AuthenticationStepsFactory.class, false).getIfAvailable(), + () -> beanFactory.getBeanProvider(ClientAuthentication.class, false).getIfAvailable()); + } + +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveBootstrapConfiguration.java index 8cf6bc85..716eb342 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultReactiveBootstrapConfiguration.java @@ -16,61 +16,25 @@ package org.springframework.cloud.vault.config; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.annotation.Order; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.vault.authentication.AuthenticationStepsFactory; -import org.springframework.vault.authentication.AuthenticationStepsOperator; -import org.springframework.vault.authentication.CachingVaultTokenSupplier; -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.SimpleVaultEndpointProvider; +import org.springframework.cloud.bootstrap.BootstrapConfiguration; 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.core.ReactiveVaultOperations; -import org.springframework.vault.core.ReactiveVaultTemplate; -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; -import static org.springframework.cloud.vault.config.VaultAutoConfiguration.TaskSchedulerWrapper; - /** - * {@link EnableAutoConfiguration Auto-configuration} for reactive Spring Vault support. + * {@link BootstrapConfiguration Bootstrap-configuration} for reactive Spring Vault + * support. *

* This auto-configuration only supports static endpoints without * {@link org.springframework.vault.client.VaultEndpointProvider} support as endpoint @@ -78,206 +42,20 @@ import static org.springframework.cloud.vault.config.VaultAutoConfiguration.Task * * @author Mark Paluch * @since 2.0.0 + * @deprecated since 3.0, use {@link VaultReactiveAutoConfiguration} through + * {@code @EnableAutoConfiguration}. */ +@Deprecated @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) @ConditionalOnExpression("${spring.cloud.vault.reactive.enabled:true}") @ConditionalOnClass({ Flux.class, WebClient.class, ReactiveVaultOperations.class, HttpClient.class }) @EnableConfigurationProperties({ VaultProperties.class }) -@Order(Ordered.LOWEST_PRECEDENCE - 10) -public class VaultReactiveBootstrapConfiguration implements InitializingBean { - - // TODO: Expose as AutoConfiguration - private final VaultProperties vaultProperties; - - private final VaultEndpointProvider endpointProvider; - - private final List customizers; - - private ClientHttpConnector clientHttpConnector; - - /** - * Used for Vault communication. - */ - private WebClientBuilder webClientBuilder; - - /** - * Used for Vault communication. - */ - private WebClientFactory webClientFactory; +public class VaultReactiveBootstrapConfiguration extends VaultReactiveAutoConfiguration { public VaultReactiveBootstrapConfiguration(VaultProperties vaultProperties, ObjectProvider endpointProvider, ObjectProvider> webClientCustomizers) { - - this.vaultProperties = vaultProperties; - - this.endpointProvider = endpointProvider.getIfAvailable( - () -> SimpleVaultEndpointProvider.of(VaultConfigurationUtil.createVaultEndpoint(vaultProperties))); - this.customizers = new ArrayList<>(webClientCustomizers.getIfAvailable(Collections::emptyList)); - AnnotationAwareOrderComparator.sort(this.customizers); - } - - @Override - public void afterPropertiesSet() { - - this.clientHttpConnector = createConnector(this.vaultProperties); - - this.webClientBuilder = webClientBuilder(this.clientHttpConnector); - - this.webClientFactory = new DefaultWebClientFactory(this.clientHttpConnector, this::webClientBuilder); - } - - protected WebClientBuilder webClientBuilder(ClientHttpConnector connector) { - - WebClientBuilder builder = WebClientBuilder.builder().httpConnector(connector) - .endpointProvider(this.endpointProvider); - - this.customizers.forEach(builder::customizers); - - if (StringUtils.hasText(this.vaultProperties.getNamespace())) { - builder.defaultHeader(VaultHttpHeaders.VAULT_NAMESPACE, this.vaultProperties.getNamespace()); - } - - return builder; - } - - /** - * Creates a {@link ClientHttpConnector} configured with {@link ClientOptions} and - * {@link SslConfiguration} which are not necessarily applicable for the whole - * application. - * @param vaultProperties the Vault properties. - * @return the {@link ClientHttpConnector}. - */ - protected ClientHttpConnector createConnector(VaultProperties vaultProperties) { - - ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(vaultProperties.getConnectionTimeout()), - Duration.ofMillis(vaultProperties.getReadTimeout())); - - SslConfiguration sslConfiguration = VaultConfigurationUtil.createSslConfiguration(vaultProperties.getSsl()); - - return ClientHttpConnectorFactory.create(clientOptions, sslConfiguration); - } - - /** - * Create a {@link WebClientFactory} bean that is used to produce {@link WebClient}. - * @return the {@link WebClientFactory}. - * @since 3.0 - */ - @Bean - public WebClientFactory vaultWebClientFactory() { - return this.webClientFactory; - } - - /** - * Creates a {@link ReactiveVaultTemplate}. - * @return the {@link ReactiveVaultTemplate} bean. - * @see #reactiveVaultSessionManager(BeanFactory, ObjectFactory) - */ - @Bean - @ConditionalOnMissingBean(ReactiveVaultOperations.class) - public ReactiveVaultTemplate reactiveVaultTemplate(ObjectProvider sessionManager) { - - if (this.vaultProperties.getAuthentication() == VaultProperties.AuthenticationMethod.NONE) { - return new ReactiveVaultTemplate(this.webClientBuilder); - } - - return new ReactiveVaultTemplate(this.webClientBuilder, sessionManager.getObject()); - } - - /** - * @param beanFactory the {@link BeanFactory}. - * @param asyncTaskExecutorFactory the {@link ObjectFactory} for - * {@link TaskSchedulerWrapper}. - * @return {@link ReactiveSessionManager} for reactive session use. - * @see ReactiveSessionManager - * @see ReactiveLifecycleAwareSessionManager - */ - @Bean - @ConditionalOnMissingBean - @ConditionalOnAuthentication - public ReactiveSessionManager reactiveVaultSessionManager(BeanFactory beanFactory, - ObjectFactory asyncTaskExecutorFactory) { - - VaultTokenSupplier vaultTokenSupplier = beanFactory.getBean("vaultTokenSupplier", VaultTokenSupplier.class); - - VaultProperties.SessionLifecycle lifecycle = this.vaultProperties.getSession().getLifecycle(); - if (lifecycle.isEnabled()) { - WebClient webClient = this.webClientFactory.create(); - ReactiveLifecycleAwareSessionManager.RefreshTrigger trigger = new ReactiveLifecycleAwareSessionManager.FixedTimeoutRefreshTrigger( - lifecycle.getRefreshBeforeExpiry(), lifecycle.getExpiryThreshold()); - return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier, - asyncTaskExecutorFactory.getObject().getTaskScheduler(), webClient, trigger); - } - - return CachingVaultTokenSupplier.of(vaultTokenSupplier); - } - - /** - * @param sessionManager the {@link ReactiveSessionManager}. - * @return {@link SessionManager} adapter wrapping {@link ReactiveSessionManager}. - */ - @Bean - @ConditionalOnMissingBean - @ConditionalOnAuthentication - public SessionManager vaultSessionManager(ReactiveSessionManager sessionManager) { - return () -> { - - VaultToken token = sessionManager.getSessionToken().block(); - Assert.state(token != null, "ReactiveSessionManager returned a null VaultToken"); - return token; - }; - } - - /** - * @param beanFactory the {@link BeanFactory}. - * @return the {@link VaultTokenSupplier} for reactive Vault session management - * adapting {@link ClientAuthentication} that also implement - * {@link AuthenticationStepsFactory}. - * @see AuthenticationStepsFactory - */ - @Bean - @ConditionalOnMissingBean(name = "vaultTokenSupplier") - @ConditionalOnAuthentication - public VaultTokenSupplier vaultTokenSupplier(ListableBeanFactory beanFactory) { - - Assert.notNull(beanFactory, "BeanFactory must not be null"); - - String[] authStepsFactories = beanFactory.getBeanNamesForType(AuthenticationStepsFactory.class); - - if (!ObjectUtils.isEmpty(authStepsFactories)) { - - AuthenticationStepsFactory factory = beanFactory.getBean(AuthenticationStepsFactory.class); - return createAuthenticationStepsOperator(factory); - } - - String[] clientAuthentications = beanFactory.getBeanNamesForType(ClientAuthentication.class); - - if (!ObjectUtils.isEmpty(clientAuthentications)) { - - ClientAuthentication clientAuthentication = beanFactory.getBean(ClientAuthentication.class); - - if (clientAuthentication instanceof TokenAuthentication) { - - TokenAuthentication authentication = (TokenAuthentication) clientAuthentication; - return () -> Mono.just(authentication.login()); - } - - if (clientAuthentication instanceof AuthenticationStepsFactory) { - return createAuthenticationStepsOperator((AuthenticationStepsFactory) clientAuthentication); - } - - 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) { - WebClient webClient = this.webClientFactory.create(); - return new AuthenticationStepsOperator(factory.getAuthenticationSteps(), webClient); + super(vaultProperties, endpointProvider, webClientCustomizers); } }