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.
This commit is contained in:
Mark Paluch
2017-06-05 15:09:40 +02:00
parent 99085d4fd8
commit 1d7c679cbf
7 changed files with 226 additions and 5 deletions

View File

@@ -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;
}
}
}

View File

@@ -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.
* <p/>
* Lease mode is considered only by lease-aware property sources.
*
* @return the lease mode of this secret backend.
* @since 1.1
*/
Mode getLeaseMode();
}

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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)}.
*
*
* <p>
* 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<T extends VaultSecretBackendDescriptor> {
@@ -43,6 +44,7 @@ public interface SecretBackendMetadataFactory<T extends VaultSecretBackendDescri
*
* @param backendDescriptor must not be {@literal null}.
* @return the {@link SecretBackendMetadata}.
* @see LeasingSecretBackendMetadata
*/
SecretBackendMetadata createMetadata(T backendDescriptor);

View File

@@ -0,0 +1,69 @@
/*
* 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.Map;
import org.springframework.util.Assert;
import org.springframework.vault.core.util.PropertyTransformer;
/**
* Provides a convenient implementation of the {@link SecretBackendMetadata} interface
* that can be subclassed to override specific methods.
* <p/>
* 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<String, String> getVariables() {
return delegate.getVariables();
}
}

View File

@@ -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"));
}
}