From d6c6295cfa9a9fe538f0d334572284e4fbbd70f0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 13 Jul 2017 20:55:32 +0200 Subject: [PATCH] Split configuration into parts. Closes gh-135. --- .../config/ClientAuthenticationFactory.java | 170 +++++++++++ .../config/VaultBootstrapConfiguration.java | 286 +++--------------- ...BootstrapHealthIndicatorConfiguration.java | 38 +-- ...tBootstrapPropertySourceConfiguration.java | 171 +++++++++++ .../VaultHealthIndicatorConfiguration.java | 54 ++++ .../main/resources/META-INF/spring.factories | 5 +- 6 files changed, 445 insertions(+), 279 deletions(-) create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java new file mode 100644 index 00000000..c9fcd105 --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java @@ -0,0 +1,170 @@ +/* + * Copyright 2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.vault.config; + +import java.net.URI; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.BeanUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.vault.authentication.*; +import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.Nonce; +import org.springframework.vault.support.VaultToken; +import org.springframework.web.client.RestOperations; + +/** + * Factory for {@link ClientAuthentication}. + * + * @author Mark Paluch + * @since 1.1 + */ +@RequiredArgsConstructor +class ClientAuthenticationFactory { + + private final VaultProperties vaultProperties; + + private final RestOperations restOperations; + + /** + * @return a new {@link ClientAuthentication}. + */ + public ClientAuthentication createClientAuthentication() { + + switch (vaultProperties.getAuthentication()) { + + case TOKEN: + Assert.hasText(vaultProperties.getToken(), + "Token (spring.cloud.vault.token) must not be empty"); + return new TokenAuthentication(vaultProperties.getToken()); + + case APPID: + return appIdAuthentication(vaultProperties); + + case APPROLE: + return appRoleAuthentication(vaultProperties); + + case CERT: + return new ClientCertificateAuthentication(restOperations); + + case AWS_EC2: + return awsEc2Authentication(vaultProperties); + + case CUBBYHOLE: + return cubbyholeAuthentication(); + } + + throw new UnsupportedOperationException(String.format( + "Client authentication %s not supported", + vaultProperties.getAuthentication())); + } + + private ClientAuthentication appIdAuthentication(VaultProperties vaultProperties) { + + VaultProperties.AppIdProperties appId = vaultProperties.getAppId(); + Assert.hasText(appId.getUserId(), + "UserId (spring.cloud.vault.app-id.user-id) must not be empty"); + + AppIdAuthenticationOptions authenticationOptions = AppIdAuthenticationOptions + .builder().appId(vaultProperties.getApplicationName()) // + .path(appId.getAppIdPath()) // + .userIdMechanism(getClientAuthentication(appId)).build(); + + return new AppIdAuthentication(authenticationOptions, restOperations); + } + + private AppIdUserIdMechanism getClientAuthentication( + VaultProperties.AppIdProperties appId) { + + try { + Class userIdClass = ClassUtils.forName(appId.getUserId(), null); + return (AppIdUserIdMechanism) BeanUtils.instantiateClass(userIdClass); + } + catch (ClassNotFoundException ex) { + + switch (appId.getUserId().toUpperCase()) { + + case VaultProperties.AppIdProperties.IP_ADDRESS: + return new IpAddressUserId(); + + case VaultProperties.AppIdProperties.MAC_ADDRESS: + + if (StringUtils.hasText(appId.getNetworkInterface())) { + try { + return new MacAddressUserId(Integer.parseInt(appId + .getNetworkInterface())); + } + catch (NumberFormatException e) { + return new MacAddressUserId(appId.getNetworkInterface()); + } + } + + return new MacAddressUserId(); + default: + return new StaticUserId(appId.getUserId()); + } + } + } + + private ClientAuthentication appRoleAuthentication(VaultProperties vaultProperties) { + + VaultProperties.AppRoleProperties appRole = vaultProperties.getAppRole(); + Assert.hasText(appRole.getRoleId(), + "RoleId (spring.cloud.vault.app-role.role-id) must not be empty"); + + AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions + .builder().path(appRole.getAppRolePath()).roleId(appRole.getRoleId()); + + if (StringUtils.hasText(appRole.getSecretId())) { + builder = builder.secretId(appRole.getSecretId()); + } + + return new AppRoleAuthentication(builder.build(), restOperations); + } + + private ClientAuthentication awsEc2Authentication(VaultProperties vaultProperties) { + + VaultProperties.AwsEc2Properties awsEc2 = vaultProperties.getAwsEc2(); + + Nonce nonce = StringUtils.hasText(awsEc2.getNonce()) ? Nonce.provided(awsEc2 + .getNonce().toCharArray()) : Nonce.generated(); + + AwsEc2AuthenticationOptions authenticationOptions = AwsEc2AuthenticationOptions + .builder().role(awsEc2.getRole()) // + .path(awsEc2.getAwsEc2Path()) // + .nonce(nonce) // + .identityDocumentUri(URI.create(awsEc2.getIdentityDocument())) // + .build(); + + return new AwsEc2Authentication(authenticationOptions, restOperations, + restOperations); + } + + private ClientAuthentication cubbyholeAuthentication() { + + Assert.hasText(vaultProperties.getToken(), + "Initial Token (spring.cloud.vault.token) for Cubbyhole authentication must not be empty"); + + CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder() // + .wrapped() // + .initialToken(VaultToken.of(vaultProperties.getToken())) // + .build(); + + return new CubbyholeAuthentication(options, restOperations); + } +} 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 092e3564..930c4a69 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 @@ -15,15 +15,9 @@ */ package org.springframework.cloud.vault.config; -import static org.springframework.cloud.vault.config.GenericSecretBackendMetadata.create; - import java.net.URI; import java.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ObjectFactory; @@ -31,7 +25,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -42,39 +35,19 @@ 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.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import org.springframework.vault.authentication.AppIdAuthentication; -import org.springframework.vault.authentication.AppIdAuthenticationOptions; -import org.springframework.vault.authentication.AppIdUserIdMechanism; -import org.springframework.vault.authentication.AppRoleAuthentication; -import org.springframework.vault.authentication.AppRoleAuthenticationOptions; -import org.springframework.vault.authentication.AwsEc2Authentication; -import org.springframework.vault.authentication.AwsEc2AuthenticationOptions; -import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.Nonce; import org.springframework.vault.authentication.ClientAuthentication; -import org.springframework.vault.authentication.ClientCertificateAuthentication; -import org.springframework.vault.authentication.CubbyholeAuthentication; -import org.springframework.vault.authentication.CubbyholeAuthenticationOptions; -import org.springframework.vault.authentication.IpAddressUserId; import org.springframework.vault.authentication.LifecycleAwareSessionManager; -import org.springframework.vault.authentication.MacAddressUserId; import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.authentication.SimpleSessionManager; -import org.springframework.vault.authentication.StaticUserId; -import org.springframework.vault.authentication.TokenAuthentication; import org.springframework.vault.client.VaultClients; import org.springframework.vault.client.VaultEndpoint; -import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper; import org.springframework.vault.config.ClientHttpRequestFactoryFactory; -import org.springframework.vault.core.VaultOperations; +import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper; import org.springframework.vault.core.VaultTemplate; -import org.springframework.vault.core.lease.SecretLeaseContainer; import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration; -import org.springframework.vault.support.VaultToken; import org.springframework.web.client.RestOperations; /** @@ -85,8 +58,8 @@ import org.springframework.web.client.RestOperations; */ @Configuration @ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) -@EnableConfigurationProperties({ VaultProperties.class, VaultGenericBackendProperties.class }) -@Order(Ordered.LOWEST_PRECEDENCE - 10) +@EnableConfigurationProperties(VaultProperties.class) +@Order(Ordered.LOWEST_PRECEDENCE - 5) public class VaultBootstrapConfiguration implements InitializingBean { private final ConfigurableApplicationContext applicationContext; @@ -97,10 +70,6 @@ public class VaultBootstrapConfiguration implements InitializingBean { private RestOperations restOperations; - private Collection vaultSecretBackendDescriptors; - - private Collection> factories; - public VaultBootstrapConfiguration(ConfigurableApplicationContext applicationContext, VaultProperties vaultProperties) { @@ -125,104 +94,32 @@ public class VaultBootstrapConfiguration implements InitializingBean { @Override @SuppressWarnings("unchecked") - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { - this.vaultSecretBackendDescriptors = applicationContext.getBeansOfType(VaultSecretBackendDescriptor.class).values(); + ClientHttpRequestFactory clientHttpRequestFactory = clientHttpRequestFactoryWrapper() + .getClientHttpRequestFactory(); - this.factories = (Collection) applicationContext.getBeansOfType(SecretBackendMetadataFactory.class).values(); - - ClientHttpRequestFactory clientHttpRequestFactory = clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(); - - this.restOperations = VaultClients.createRestTemplate(vaultEndpoint, clientHttpRequestFactory); - } - - @Bean - public PropertySourceLocator vaultPropertySourceLocator(VaultOperations operations, VaultProperties vaultProperties, - VaultGenericBackendProperties vaultGenericBackendProperties, - ObjectFactory secretLeaseContainerObjectFactory) { - - VaultConfigTemplate vaultConfigTemplate = new VaultConfigTemplate(operations, vaultProperties); - - PropertySourceLocatorConfiguration propertySourceLocatorConfiguration = getPropertySourceConfiguration( - vaultGenericBackendProperties); - - if (vaultProperties.getConfig().getLifecycle().isEnabled()) { - - // This is to destroy bootstrap resources - // otherwise, the bootstrap context is not shut down cleanly - applicationContext.registerShutdownHook(); - - SecretLeaseContainer secretLeaseContainer = secretLeaseContainerObjectFactory.getObject(); - secretLeaseContainer.start(); - - return new LeasingVaultPropertySourceLocator(vaultProperties, propertySourceLocatorConfiguration, - secretLeaseContainer); - } - - return new VaultPropertySourceLocator(vaultConfigTemplate, vaultProperties, propertySourceLocatorConfiguration); - } - - private PropertySourceLocatorConfiguration getPropertySourceConfiguration( - VaultGenericBackendProperties vaultGenericBackendProperties) { - - Collection configurers = applicationContext.getBeansOfType(VaultConfigurer.class).values(); - - DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer(); - - if (configurers.isEmpty()) { - secretBackendConfigurer.registerDefaultGenericSecretBackends(true).registerDefaultDiscoveredSecretBackends(true); - } else { - - for (VaultConfigurer vaultConfigurer : configurers) { - vaultConfigurer.addSecretBackends(secretBackendConfigurer); - } - } - - if (secretBackendConfigurer.isRegisterDefaultGenericSecretBackends()) { - - if (vaultGenericBackendProperties.isEnabled()) { - - List contexts = GenericSecretBackendMetadata.buildContexts(vaultGenericBackendProperties, - Arrays.asList(applicationContext.getEnvironment().getActiveProfiles())); - - for (String context : contexts) { - secretBackendConfigurer.add(create(vaultGenericBackendProperties.getBackend(), context)); - } - } - - Collection backendAccessors = SecretBackendFactories - .createSecretBackendMetadata(vaultSecretBackendDescriptors, factories); - for (SecretBackendMetadata metadata : backendAccessors) { - secretBackendConfigurer.add(metadata); - } - } - - if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) { - - Collection backendAccessors = SecretBackendFactories - .createSecretBackendMetadata(vaultSecretBackendDescriptors, factories); - for (SecretBackendMetadata metadata : backendAccessors) { - secretBackendConfigurer.add(metadata); - } - } - - return secretBackendConfigurer; + this.restOperations = VaultClients.createRestTemplate(vaultEndpoint, + clientHttpRequestFactory); } /** - * Creates a {@link ClientFactoryWrapper} containing a {@link ClientHttpRequestFactory}. - * {@link ClientHttpRequestFactory} is not exposed as root bean because {@link ClientHttpRequestFactory} is configured - * with {@link ClientOptions} and {@link SslConfiguration} which are not necessarily applicable for the whole - * application. + * Creates a {@link ClientFactoryWrapper} containing a + * {@link ClientHttpRequestFactory}. {@link ClientHttpRequestFactory} is not exposed + * as root bean because {@link ClientHttpRequestFactory} is configured with + * {@link ClientOptions} and {@link SslConfiguration} which are not necessarily + * applicable for the whole application. * - * @return the {@link ClientFactoryWrapper} to wrap a {@link ClientHttpRequestFactory} instance. + * @return the {@link ClientFactoryWrapper} to wrap a {@link ClientHttpRequestFactory} + * instance. */ @Bean @ConditionalOnMissingBean public ClientFactoryWrapper clientHttpRequestFactoryWrapper() { - ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(vaultProperties.getConnectionTimeout()), - Duration.ofMillis(vaultProperties.getReadTimeout())); + ClientOptions clientOptions = new ClientOptions(Duration.ofMillis(vaultProperties + .getConnectionTimeout()), Duration.ofMillis(vaultProperties + .getReadTimeout())); VaultProperties.Ssl ssl = vaultProperties.getSsl(); SslConfiguration sslConfiguration; @@ -233,20 +130,24 @@ public class VaultBootstrapConfiguration implements InitializingBean { if (ssl.getKeyStore() != null) { keyStore = new KeyStoreConfiguration(ssl.getKeyStore(), - ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null, null); + ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword() + .toCharArray() : null, null); } if (ssl.getTrustStore() != null) { trustStore = new KeyStoreConfiguration(ssl.getTrustStore(), - ssl.getTrustStorePassword() != null ? ssl.getTrustStorePassword().toCharArray() : null, null); + ssl.getTrustStorePassword() != null ? ssl.getTrustStorePassword() + .toCharArray() : null, null); } sslConfiguration = new SslConfiguration(keyStore, trustStore); - } else { + } + else { sslConfiguration = SslConfiguration.NONE; } - return new ClientFactoryWrapper(ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration)); + return new ClientFactoryWrapper(ClientHttpRequestFactoryFactory.create( + clientOptions, sslConfiguration)); } /** @@ -258,13 +159,13 @@ public class VaultBootstrapConfiguration implements InitializingBean { @Bean @ConditionalOnMissingBean public VaultTemplate vaultTemplate(SessionManager sessionManager) { - return new VaultTemplate(vaultEndpoint, clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(), - sessionManager); + return new VaultTemplate(vaultEndpoint, clientHttpRequestFactoryWrapper() + .getClientHttpRequestFactory(), sessionManager); } /** - * Creates a new {@link TaskSchedulerWrapper} that encapsulates a bean implementing {@link TaskScheduler} and - * {@link AsyncTaskExecutor}. + * Creates a new {@link TaskSchedulerWrapper} that encapsulates a bean implementing + * {@link TaskScheduler} and {@link AsyncTaskExecutor}. * * @return * @see ThreadPoolTaskScheduler @@ -298,140 +199,27 @@ public class VaultBootstrapConfiguration implements InitializingBean { if (vaultProperties.getConfig().getLifecycle().isEnabled()) { return new LifecycleAwareSessionManager(clientAuthentication, - asyncTaskExecutorFactory.getObject().getTaskScheduler(), restOperations); + asyncTaskExecutorFactory.getObject().getTaskScheduler(), + restOperations); } return new SimpleSessionManager(clientAuthentication); } /** - * @return the {@link SessionManager} for Vault session management. + * @return the {@link ClientAuthentication} to obtain a + * {@link org.springframework.vault.support.VaultToken}. * @see SessionManager * @see LifecycleAwareSessionManager */ - @Bean - @Lazy - @ConditionalOnMissingBean - public SecretLeaseContainer secretLeaseContainer(VaultOperations vaultOperations, - TaskSchedulerWrapper taskSchedulerWrapper) { - return new SecretLeaseContainer(vaultOperations, taskSchedulerWrapper.getTaskScheduler()); - } - @Bean @ConditionalOnMissingBean public ClientAuthentication clientAuthentication() { - switch (vaultProperties.getAuthentication()) { + ClientAuthenticationFactory factory = new ClientAuthenticationFactory( + vaultProperties, restOperations); - case TOKEN: - Assert.hasText(vaultProperties.getToken(), "Token (spring.cloud.vault.token) must not be empty"); - return new TokenAuthentication(vaultProperties.getToken()); - - case APPID: - return appIdAuthentication(vaultProperties); - - case APPROLE: - return appRoleAuthentication(vaultProperties); - - case CERT: - return new ClientCertificateAuthentication(restOperations); - - case AWS_EC2: - return awsEc2Authentication(vaultProperties); - - case CUBBYHOLE: - return cubbyholeAuthentication(); - - } - - throw new UnsupportedOperationException( - String.format("Client authentication %s not supported", vaultProperties.getAuthentication())); - } - - private ClientAuthentication appIdAuthentication(VaultProperties vaultProperties) { - - VaultProperties.AppIdProperties appId = vaultProperties.getAppId(); - Assert.hasText(appId.getUserId(), "UserId (spring.cloud.vault.app-id.user-id) must not be empty"); - - AppIdAuthenticationOptions authenticationOptions = AppIdAuthenticationOptions.builder() - .appId(vaultProperties.getApplicationName()) // - .path(appId.getAppIdPath()) // - .userIdMechanism(getClientAuthentication(appId)).build(); - - return new AppIdAuthentication(authenticationOptions, restOperations); - } - - private AppIdUserIdMechanism getClientAuthentication(VaultProperties.AppIdProperties appId) { - - try { - Class userIdClass = ClassUtils.forName(appId.getUserId(), null); - return (AppIdUserIdMechanism) BeanUtils.instantiateClass(userIdClass); - } catch (ClassNotFoundException ex) { - - switch (appId.getUserId().toUpperCase()) { - - case VaultProperties.AppIdProperties.IP_ADDRESS: - return new IpAddressUserId(); - - case VaultProperties.AppIdProperties.MAC_ADDRESS: - - if (StringUtils.hasText(appId.getNetworkInterface())) { - try { - return new MacAddressUserId(Integer.parseInt(appId.getNetworkInterface())); - } catch (NumberFormatException e) { - return new MacAddressUserId(appId.getNetworkInterface()); - } - } - - return new MacAddressUserId(); - default: - return new StaticUserId(appId.getUserId()); - } - } - } - - private ClientAuthentication appRoleAuthentication(VaultProperties vaultProperties) { - - VaultProperties.AppRoleProperties appRole = vaultProperties.getAppRole(); - Assert.hasText(appRole.getRoleId(), "RoleId (spring.cloud.vault.app-role.role-id) must not be empty"); - - AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions.builder() - .path(appRole.getAppRolePath()).roleId(appRole.getRoleId()); - - if (StringUtils.hasText(appRole.getSecretId())) { - builder = builder.secretId(appRole.getSecretId()); - } - - return new AppRoleAuthentication(builder.build(), restOperations); - } - - private ClientAuthentication awsEc2Authentication(VaultProperties vaultProperties) { - - VaultProperties.AwsEc2Properties awsEc2 = vaultProperties.getAwsEc2(); - - Nonce nonce = StringUtils.hasText(awsEc2.getNonce()) ? Nonce.provided(awsEc2.getNonce().toCharArray()) - : Nonce.generated(); - - AwsEc2AuthenticationOptions authenticationOptions = AwsEc2AuthenticationOptions.builder().role(awsEc2.getRole()) // - .path(awsEc2.getAwsEc2Path()) // - .nonce(nonce) // - .identityDocumentUri(URI.create(awsEc2.getIdentityDocument())) // - .build(); - - return new AwsEc2Authentication(authenticationOptions, restOperations, restOperations); - } - - private ClientAuthentication cubbyholeAuthentication() { - - Assert.hasText(vaultProperties.getToken(), - "Initial Token (spring.cloud.vault.token) for Cubbyhole authentication must not be empty"); - - CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder() // - .wrapped() // - .initialToken(VaultToken.of(vaultProperties.getToken())) // - .build(); - - return new CubbyholeAuthentication(options, restOperations); + return factory.createClientAuthentication(); } /** diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapHealthIndicatorConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapHealthIndicatorConfiguration.java index 7629d3d3..40b1fe8e 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapHealthIndicatorConfiguration.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapHealthIndicatorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2017 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. @@ -15,35 +15,15 @@ */ package org.springframework.cloud.vault.config; -import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.vault.core.VaultOperations; - /** - * @author Stuart Ingram + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for Vault providing beans for the application context. + * * @author Mark Paluch + * @author Stuart Ingram + * @deprecated since 1.1, use {@link VaultHealthIndicatorConfiguration}. */ -@Configuration -@ConditionalOnBean(VaultBootstrapConfiguration.class) -@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) -@ConditionalOnExpression("${health.vault.enabled:true}") -@AutoConfigureBefore({ EndpointAutoConfiguration.class }) -@AutoConfigureAfter({ VaultBootstrapConfiguration.class, - HealthIndicatorAutoConfiguration.class }) -public class VaultBootstrapHealthIndicatorConfiguration { - - @Bean - @ConditionalOnMissingBean(name = "vaultHealthIndicator") - public HealthIndicator vaultHealthIndicator(VaultOperations vaultOperations) { - return new VaultHealthIndicator(vaultOperations); - } +@Deprecated +public class VaultBootstrapHealthIndicatorConfiguration extends + VaultHealthIndicatorConfiguration { } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java new file mode 100644 index 00000000..139035ad --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapPropertySourceConfiguration.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.vault.config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectFactory; +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; +import org.springframework.vault.authentication.LifecycleAwareSessionManager; +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.GenericSecretBackendMetadata.*; + +/** + * {@link org.springframework.cloud.bootstrap.BootstrapConfiguration Auto-configuration} + * for Spring Vault's {@link PropertySourceLocator} support. + * + * @author Mark Paluch + * @since 1.1 + */ +@Configuration +@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@EnableConfigurationProperties(VaultGenericBackendProperties.class) +@Order(Ordered.LOWEST_PRECEDENCE - 10) +public class VaultBootstrapPropertySourceConfiguration implements InitializingBean { + + private final ConfigurableApplicationContext applicationContext; + + private Collection vaultSecretBackendDescriptors; + + private Collection> factories; + + public VaultBootstrapPropertySourceConfiguration( + ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + @SuppressWarnings("unchecked") + public void afterPropertiesSet() throws Exception { + + this.vaultSecretBackendDescriptors = applicationContext.getBeansOfType( + VaultSecretBackendDescriptor.class).values(); + + this.factories = (Collection) applicationContext.getBeansOfType( + SecretBackendMetadataFactory.class).values(); + } + + @Bean + public PropertySourceLocator vaultPropertySourceLocator(VaultOperations operations, + VaultProperties vaultProperties, + VaultGenericBackendProperties vaultGenericBackendProperties, + ObjectFactory secretLeaseContainerObjectFactory) { + + VaultConfigTemplate vaultConfigTemplate = new VaultConfigTemplate(operations, + vaultProperties); + + PropertySourceLocatorConfiguration propertySourceLocatorConfiguration = getPropertySourceConfiguration(vaultGenericBackendProperties); + + if (vaultProperties.getConfig().getLifecycle().isEnabled()) { + + // This is to destroy bootstrap resources + // otherwise, the bootstrap context is not shut down cleanly + applicationContext.registerShutdownHook(); + + SecretLeaseContainer secretLeaseContainer = secretLeaseContainerObjectFactory + .getObject(); + secretLeaseContainer.start(); + + return new LeasingVaultPropertySourceLocator(vaultProperties, + propertySourceLocatorConfiguration, secretLeaseContainer); + } + + return new VaultPropertySourceLocator(vaultConfigTemplate, vaultProperties, + propertySourceLocatorConfiguration); + } + + private PropertySourceLocatorConfiguration getPropertySourceConfiguration( + VaultGenericBackendProperties vaultGenericBackendProperties) { + + Collection configurers = applicationContext.getBeansOfType( + VaultConfigurer.class).values(); + + DefaultSecretBackendConfigurer secretBackendConfigurer = new DefaultSecretBackendConfigurer(); + + if (configurers.isEmpty()) { + secretBackendConfigurer.registerDefaultGenericSecretBackends(true) + .registerDefaultDiscoveredSecretBackends(true); + } + else { + + for (VaultConfigurer vaultConfigurer : configurers) { + vaultConfigurer.addSecretBackends(secretBackendConfigurer); + } + } + + if (secretBackendConfigurer.isRegisterDefaultGenericSecretBackends()) { + + if (vaultGenericBackendProperties.isEnabled()) { + + List contexts = GenericSecretBackendMetadata.buildContexts( + vaultGenericBackendProperties, Arrays.asList(applicationContext + .getEnvironment().getActiveProfiles())); + + for (String context : contexts) { + secretBackendConfigurer.add(create( + vaultGenericBackendProperties.getBackend(), context)); + } + } + + Collection backendAccessors = SecretBackendFactories + .createSecretBackendMetadata(vaultSecretBackendDescriptors, factories); + for (SecretBackendMetadata metadata : backendAccessors) { + secretBackendConfigurer.add(metadata); + } + } + + if (secretBackendConfigurer.isRegisterDefaultDiscoveredSecretBackends()) { + + Collection backendAccessors = SecretBackendFactories + .createSecretBackendMetadata(vaultSecretBackendDescriptors, factories); + for (SecretBackendMetadata metadata : backendAccessors) { + secretBackendConfigurer.add(metadata); + } + } + + return secretBackendConfigurer; + } + + /** + * @return the {@link SessionManager} for Vault session management. + * @see SessionManager + * @see LifecycleAwareSessionManager + */ + @Bean + @Lazy + @ConditionalOnMissingBean + public SecretLeaseContainer secretLeaseContainer(VaultOperations vaultOperations, + TaskSchedulerWrapper taskSchedulerWrapper) { + return new SecretLeaseContainer(vaultOperations, + taskSchedulerWrapper.getTaskScheduler()); + } +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java new file mode 100644 index 00000000..d28bc7bc --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultHealthIndicatorConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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 + * + * http://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.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.vault.core.VaultOperations; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for Vault providing beans for the application context. + * + * @author Stuart Ingram + * @author Mark Paluch + * @since 1.1 + */ +@Configuration +@ConditionalOnClass(HealthIndicator.class) +@ConditionalOnBean(VaultBootstrapConfiguration.class) +@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true) +@ConditionalOnExpression("${health.vault.enabled:true}") +@AutoConfigureBefore({ EndpointAutoConfiguration.class }) +@AutoConfigureAfter(HealthIndicatorAutoConfiguration.class) +public class VaultHealthIndicatorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "vaultHealthIndicator") + public HealthIndicator vaultHealthIndicator(VaultOperations vaultOperations) { + return new VaultHealthIndicator(vaultOperations); + } +} diff --git a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories index b947ca13..1ce5dd5e 100644 --- a/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-vault-config/src/main/resources/META-INF/spring.factories @@ -1,5 +1,8 @@ +# Auto-Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.vault.config.VaultHealthIndicatorConfiguration # Bootstrap Configuration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.vault.config.VaultBootstrapConfiguration,\ org.springframework.cloud.vault.config.ReactiveVaultBootstrapConfiguration,\ -org.springframework.cloud.vault.config.VaultBootstrapHealthIndicatorConfiguration +org.springframework.cloud.vault.config.VaultBootstrapPropertySourceConfiguration