From edb14d6ac85d1806155b08071803c86a25230355 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Jan 2018 15:54:15 +0100 Subject: [PATCH] Adapt to newly introduced ReactiveSessionManager. Closes gh-188. --- .../ReactiveVaultBootstrapConfiguration.java | 113 ++++++++++++++++-- .../config/VaultBootstrapConfiguration.java | 2 +- .../PropertyNameTransformerUnitTests.java | 8 +- ...ctiveVaultBootstrapConfigurationTests.java | 31 ++++- 4 files changed, 136 insertions(+), 18 deletions(-) diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java index 1136b3aa..d4d281cd 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java @@ -19,21 +19,34 @@ import java.net.URI; import java.time.Duration; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.ObjectFactory; 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.cloud.vault.config.VaultBootstrapConfiguration.TaskSchedulerWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; 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.ReactiveVaultClients; import org.springframework.vault.client.VaultEndpoint; @@ -47,12 +60,17 @@ import org.springframework.web.reactive.function.client.WebClient; /** * {@link EnableAutoConfiguration Auto-configuration} for reactive Spring Vault support. + *

+ * This auto-configuration only supports static endpoints without + * {@link org.springframework.vault.client.VaultEndpointProvider} support as endpoint + * providers could be potentially blocking implementations. * * @author Mark Paluch * @since 2.0.0 */ @Configuration @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@ConditionalOnExpression("${spring.cloud.vault.reactive.enabled:true}") @ConditionalOnClass({ Flux.class, WebClient.class, ReactiveVaultOperations.class }) @EnableConfigurationProperties({ VaultProperties.class }) @Order(Ordered.LOWEST_PRECEDENCE - 10) @@ -139,28 +157,105 @@ public class ReactiveVaultBootstrapConfiguration { * Creates a {@link ReactiveVaultTemplate}. * * @return - * @see #vaultTokenSupplier(AuthenticationStepsFactory) + * @see #reactiveVaultSessionManager(BeanFactory, ObjectFactory) */ @Bean @ConditionalOnMissingBean - public ReactiveVaultTemplate reactiveVaultTemplate(VaultTokenSupplier tokenSupplier) { + public ReactiveVaultTemplate reactiveVaultTemplate( + ReactiveSessionManager tokenSupplier) { return new ReactiveVaultTemplate(vaultEndpoint, clientHttpConnector, tokenSupplier); } /** - * @return the {@link VaultTokenSupplier} for reactive Vault session management. + * @return {@link ReactiveSessionManager} for reactive session use. + * @see ReactiveSessionManager + * @see ReactiveLifecycleAwareSessionManager + */ + @Bean + @ConditionalOnMissingBean + public ReactiveSessionManager reactiveVaultSessionManager(BeanFactory beanFactory, + ObjectFactory asyncTaskExecutorFactory) { + + VaultTokenSupplier vaultTokenSupplier = beanFactory.getBean("vaultTokenSupplier", + VaultTokenSupplier.class); + + if (vaultProperties.getConfig().getLifecycle().isEnabled()) { + + WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint, + clientHttpConnector); + return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier, + asyncTaskExecutorFactory.getObject().getTaskScheduler(), webClient); + } + + return CachingVaultTokenSupplier.of(vaultTokenSupplier); + } + + /** + * @return {@link SessionManager} adapter wrapping {@link ReactiveSessionManager}. + */ + @Bean + @ConditionalOnMissingBean + public SessionManager vaultSessionManager(ReactiveSessionManager sessionManager) { + return sessionManager.getSessionToken()::block; + } + + /** + * @return the {@link VaultTokenSupplier} for reactive Vault session management + * adapting {@link ClientAuthentication} that also implement + * {@link AuthenticationStepsFactory}. * @see AuthenticationStepsFactory */ @Bean - @ConditionalOnMissingBean(VaultTokenSupplier.class) - public VaultTokenSupplier vaultTokenSupplier(AuthenticationStepsFactory factory) { + @ConditionalOnMissingBean(name = "vaultTokenSupplier") + 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 = ReactiveVaultClients.createWebClient(this.vaultEndpoint, this.clientHttpConnector); - AuthenticationStepsOperator operator = new AuthenticationStepsOperator( - factory.getAuthenticationSteps(), webClient); - - return CachingVaultTokenSupplier.of(operator); + return new AuthenticationStepsOperator(factory.getAuthenticationSteps(), + webClient); } } 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 1f6eb841..6f484b21 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 @@ -215,7 +215,7 @@ public class VaultBootstrapConfiguration implements InitializingBean { */ @Bean @ConditionalOnMissingBean - public SessionManager sessionManager(ClientAuthentication clientAuthentication, + public SessionManager vaultSessionManager(ClientAuthentication clientAuthentication, ObjectFactory asyncTaskExecutorFactory) { if (vaultProperties.getConfig().getLifecycle().isEnabled()) { diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java index 09e43071..246881a2 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java @@ -15,13 +15,13 @@ */ package org.springframework.cloud.vault.config; -import static org.assertj.core.api.Assertions.*; - import java.util.HashMap; import java.util.Map; import org.junit.Test; +import static org.assertj.core.api.Assertions.*; + /** * Unit tests for {@link PropertyNameTransformer}. * @@ -35,7 +35,7 @@ public class PropertyNameTransformerUnitTests { PropertyNameTransformer transformer = new PropertyNameTransformer(); transformer.addKeyTransformation("old-key", "new-key"); - Map map = new HashMap<>(); + Map map = new HashMap<>(); map.put("old-key", "value"); map.put("other-key", "other-value"); @@ -51,4 +51,4 @@ public class PropertyNameTransformerUnitTests { assertThat(transformer.transformProperties(null)).isNull(); } -} \ No newline at end of file +} diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java index ef6b50ab..572ceea9 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 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. @@ -24,6 +24,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.vault.authentication.AuthenticationSteps; import org.springframework.vault.authentication.AuthenticationStepsFactory; +import org.springframework.vault.authentication.LifecycleAwareSessionManager; +import org.springframework.vault.authentication.SessionManager; +import org.springframework.vault.authentication.SimpleSessionManager; import org.springframework.vault.authentication.VaultTokenSupplier; import org.springframework.vault.core.ReactiveVaultOperations; import org.springframework.vault.support.VaultToken; @@ -43,22 +46,42 @@ public class ReactiveVaultBootstrapConfigurationTests { @Test public void shouldConfigureTemplate() { - load(AuthenticationFactoryConfiguration.class); + load(AuthenticationFactoryConfiguration.class, + "spring.cloud.vault.config.lifecycle.enabled=false"); assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull(); assertThat(context.getBean(AuthenticationStepsFactory.class)).isNotNull(); + assertThat(context.getBean(SessionManager.class)).isNotNull() + .isNotInstanceOf(LifecycleAwareSessionManager.class) + .isNotInstanceOf(SimpleSessionManager.class); assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty(); } @Test public void shouldConfigureTemplateWithTokenSupplier() { - load(TokeSupplierConfiguration.class); + load(TokenSupplierConfiguration.class, + "spring.cloud.vault.config.lifecycle.enabled=false"); assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull(); + assertThat(context.getBean(SessionManager.class)).isNotNull() + .isNotInstanceOf(LifecycleAwareSessionManager.class) + .isNotInstanceOf(SimpleSessionManager.class); assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty(); } + @Test + public void shouldNotConfigureReactiveSupport() { + + load(VaultBootstrapConfiguration.class, + "spring.cloud.vault.reactive.enabled=false", + "spring.cloud.vault.token=foo"); + + assertThat(context.getBeanNamesForType(ReactiveVaultOperations.class)).isEmpty(); + assertThat(context.getBean(SessionManager.class)).isInstanceOf( + LifecycleAwareSessionManager.class); + } + private void load(Class config, String... environment) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -82,7 +105,7 @@ public class ReactiveVaultBootstrapConfigurationTests { } @Configuration - static class TokeSupplierConfiguration { + static class TokenSupplierConfiguration { @Bean VaultTokenSupplier vaultTokenSupplier() {