From 1d7c679cbfb9899b4ec069f7f72d9ff67f9118e3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 5 Jun 2017 15:09:40 +0200 Subject: [PATCH] Support programmatic configuration of backends with lease via SecretBackendConfigurer. We now allow lease mode configuration via SecretBackendConfigurer accepting RequestedSecret. Components providing SecretBackendMetadata may also provide metadata with Lease details via LeasingSecretBackendMetadata. LeasingVaultPropertySourceLocator picks up the configured lease mode. If lifecycle-management is disabled, the lease mode is ignored. See gh-116, gh-110. --- .../DefaultSecretBackendConfigurer.java | 49 ++++++++++++- .../config/LeasingSecretBackendMetadata.java | 39 +++++++++++ .../LeasingVaultPropertySourceLocator.java | 20 +++++- .../vault/config/SecretBackendConfigurer.java | 23 +++++++ .../config/SecretBackendMetadataFactory.java | 4 +- .../config/SecretBackendMetadataWrapper.java | 69 +++++++++++++++++++ ...ngVaultPropertySourceLocatorUnitTests.java | 27 ++++++++ 7 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingSecretBackendMetadata.java create mode 100644 spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataWrapper.java diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java index 9d3a1cbe..ee655a06 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/DefaultSecretBackendConfigurer.java @@ -24,6 +24,8 @@ import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.util.Assert; +import org.springframework.vault.core.lease.domain.RequestedSecret; +import org.springframework.vault.core.lease.domain.RequestedSecret.Mode; import org.springframework.vault.core.util.PropertyTransformer; import org.springframework.vault.core.util.PropertyTransformers; @@ -57,7 +59,12 @@ class DefaultSecretBackendConfigurer Assert.hasLength(path, "Path must not be empty"); Assert.notNull(propertyTransformer, "PropertyTransformer must not be null"); - return add(new SimpleSecretBackendMetadata(path, propertyTransformer)); + return add(createMetadata(path, propertyTransformer)); + } + + private SimpleSecretBackendMetadata createMetadata(String path, + PropertyTransformer propertyTransformer) { + return new SimpleSecretBackendMetadata(path, propertyTransformer); } @Override @@ -70,6 +77,29 @@ class DefaultSecretBackendConfigurer return this; } + @Override + public SecretBackendConfigurer add(RequestedSecret requestedSecret) { + + Assert.notNull(requestedSecret, "RequestedSecret must not be null"); + + return add(requestedSecret, PropertyTransformers.noop()); + } + + @Override + public SecretBackendConfigurer add(RequestedSecret requestedSecret, + PropertyTransformer propertyTransformer) { + + Assert.notNull(requestedSecret, "RequestedSecret must not be null"); + Assert.notNull(propertyTransformer, "PropertyTransformer must not be null"); + + secretBackends.put(requestedSecret.getPath(), + new SimpleLeasingSecretBackendMetadata( + createMetadata(requestedSecret.getPath(), propertyTransformer), + requestedSecret.getMode())); + + return this; + } + @Override public SecretBackendConfigurer registerDefaultGenericSecretBackends( boolean registerDefault) { @@ -128,4 +158,21 @@ class DefaultSecretBackendConfigurer return Collections.singletonMap("path", path); } } + + private static class SimpleLeasingSecretBackendMetadata + extends SecretBackendMetadataWrapper implements LeasingSecretBackendMetadata { + + private final Mode mode; + + SimpleLeasingSecretBackendMetadata(SecretBackendMetadata delegate, Mode mode) { + + super(delegate); + this.mode = mode; + } + + @Override + public Mode getLeaseMode() { + return mode; + } + } } diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingSecretBackendMetadata.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingSecretBackendMetadata.java new file mode 100644 index 00000000..ca26dd8d --- /dev/null +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingSecretBackendMetadata.java @@ -0,0 +1,39 @@ +/* + * 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 org.springframework.vault.core.lease.domain.RequestedSecret.Mode; + +/** + * Lease extension to {@link SecretBackendMetadata} providing a + * {@link org.springframework.vault.core.lease.domain.RequestedSecret.Mode lease mode}. + * + * @author Mark Paluch + * @since 1.1 + * @see org.springframework.vault.core.lease.domain.RequestedSecret + */ +public interface LeasingSecretBackendMetadata extends SecretBackendMetadata { + + /** + * Return the lease mode of this secret backend. + *

+ * Lease mode is considered only by lease-aware property sources. + * + * @return the lease mode of this secret backend. + * @since 1.1 + */ + Mode getLeaseMode(); +} diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java index 87b74658..d71a3213 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocator.java @@ -80,9 +80,7 @@ class LeasingVaultPropertySourceLocator extends VaultPropertySourceLocatorSuppor protected PropertySource createVaultPropertySource( SecretBackendMetadata accessor) { - RequestedSecret secret = accessor instanceof GenericSecretBackendMetadata - ? RequestedSecret.rotating(accessor.getPath()) - : RequestedSecret.renewable(accessor.getPath()); + RequestedSecret secret = getRequestedSecret(accessor); if (properties.isFailFast()) { return createVaultPropertySourceFailFast(secret, accessor); @@ -91,6 +89,22 @@ class LeasingVaultPropertySourceLocator extends VaultPropertySourceLocatorSuppor return createVaultPropertySource(secret, accessor); } + private RequestedSecret getRequestedSecret(SecretBackendMetadata accessor) { + + if (accessor instanceof LeasingSecretBackendMetadata) { + + LeasingSecretBackendMetadata leasingBackend = (LeasingSecretBackendMetadata) accessor; + return RequestedSecret.from(leasingBackend.getLeaseMode(), + accessor.getPath()); + } + + if (accessor instanceof GenericSecretBackendMetadata) { + return RequestedSecret.rotating(accessor.getPath()); + } + + return RequestedSecret.renewable(accessor.getPath()); + } + /** * Decorated {@link PropertySource} creation to catch and throw the first error that * occurred during initial secret retrieval. diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java index 53367274..dc109f18 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendConfigurer.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.vault.config; +import org.springframework.vault.core.lease.domain.RequestedSecret; import org.springframework.vault.core.util.PropertyTransformer; /** @@ -63,6 +64,28 @@ public interface SecretBackendConfigurer { */ SecretBackendConfigurer add(SecretBackendMetadata metadata); + /** + * Add a {@link SecretBackendMetadata} given {@link RequestedSecret}. Property sources + * supporting leasing will derive lease renewal/rotation from + * {@link RequestedSecret.Mode}. + * + * @param requestedSecret must not be {@literal null} or empty. + * @return {@code this} {@link SecretBackendConfigurer}. + */ + SecretBackendConfigurer add(RequestedSecret requestedSecret); + + /** + * Add a {@link SecretBackendMetadata} given {@link RequestedSecret} and + * {@link PropertyTransformer}. Property sources supporting leasing will derive lease + * renewal/rotation from {@link RequestedSecret.Mode}. + * + * @param requestedSecret must not be {@literal null} or empty. + * @param propertyTransformer must not be {@literal null}. + * @return {@code this} {@link SecretBackendConfigurer}. + */ + SecretBackendConfigurer add(RequestedSecret requestedSecret, + PropertyTransformer propertyTransformer); + /** * Register default generic secret backend property sources. * diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataFactory.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataFactory.java index 61bfa65e..10b343ee 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataFactory.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/SecretBackendMetadataFactory.java @@ -26,13 +26,14 @@ package org.springframework.cloud.vault.config; * {@link VaultSecretBackendDescriptor} instance is supported by the implementation, it * must be able to create {@link SecretBackendMetadata}, see * {@link #createMetadata(VaultSecretBackendDescriptor)}. - * + * *

* Typically implemented by secret backend providers that implement access to a particular * backend using read operations. * * @author Mark Paluch * @see SecretBackendMetadata + * @see LeasingSecretBackendMetadata * @see VaultSecretBackendDescriptor */ public interface SecretBackendMetadataFactory { @@ -43,6 +44,7 @@ public interface SecretBackendMetadataFactory + * This class implements the Wrapper or Decorator pattern. Methods default to calling + * through to the wrapped request object. + * + * @author Mark Paluch + * @since 1.1 + */ +public class SecretBackendMetadataWrapper implements SecretBackendMetadata { + + private final SecretBackendMetadata delegate; + + /** + * Create a new {@link SecretBackendMetadataWrapper} given + * {@link SecretBackendMetadata}. + * + * @param delegate must not be {@literal null}. + */ + public SecretBackendMetadataWrapper(SecretBackendMetadata delegate) { + + Assert.notNull(delegate, "SecretBackendMetadata delegate must not be null"); + + this.delegate = delegate; + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getPath() { + return delegate.getPath(); + } + + @Override + public PropertyTransformer getPropertyTransformer() { + return delegate.getPropertyTransformer(); + } + + @Override + public Map getVariables() { + return delegate.getVariables(); + } +} diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java index ee51204c..f57cbf37 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/LeasingVaultPropertySourceLocatorUnitTests.java @@ -25,8 +25,10 @@ import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; import org.springframework.vault.core.lease.SecretLeaseContainer; +import org.springframework.vault.core.lease.domain.RequestedSecret; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -80,5 +82,30 @@ public class LeasingVaultPropertySourceLocatorUnitTests { CompositePropertySource composite = (CompositePropertySource) propertySource; assertThat(composite.getPropertySources()).hasSize(1); + verify(secretLeaseContainer) + .addRequestedSecret(RequestedSecret.rotating("secret/application")); + } + + @Test + public void shouldLocateLeaseAwareSources() { + + RequestedSecret rotating = RequestedSecret.rotating("secret/rotating"); + DefaultSecretBackendConfigurer configurer = new DefaultSecretBackendConfigurer(); + configurer.add(rotating); + configurer.add("database/mysql/creds/readonly"); + + propertySourceLocator = new LeasingVaultPropertySourceLocator( + new VaultProperties(), configurer, secretLeaseContainer); + + when(configurableEnvironment.getActiveProfiles()).thenReturn(new String[0]); + + PropertySource propertySource = propertySourceLocator + .locate(configurableEnvironment); + + assertThat(propertySource).isInstanceOf(CompositePropertySource.class); + + verify(secretLeaseContainer).addRequestedSecret(rotating); + verify(secretLeaseContainer).addRequestedSecret( + RequestedSecret.renewable("database/mysql/creds/readonly")); } }