Add ignoreSecretNotFound to VaultPropertySource.
Setting ignoreSecretNotFound to in VaultPropertySource allows failing on missing secrets or silently ignoring missing secrets. VaultPropertySource already ignored failures to retrieve a secret such as not found errors. The newly introduced attribute allows to fail on missing secrets. ignoreSecretNotFound defaults to true to keep previous behavior. ignoreSecretNotFound will be switched to false in a future major release. Closes gh-471.
This commit is contained in:
@@ -69,6 +69,8 @@ import org.springframework.context.annotation.Import;
|
||||
* MutablePropertySources} javadocs for details.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see org.springframework.vault.core.env.VaultPropertySource
|
||||
* @see org.springframework.vault.core.env.LeaseAwareVaultPropertySource
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -78,7 +80,7 @@ import org.springframework.context.annotation.Import;
|
||||
public @interface VaultPropertySource {
|
||||
|
||||
/**
|
||||
* Indicate the Vault path(s) of the properties to be retrieved. For example,
|
||||
* Indicate the Vault path(s) of the secret to be retrieved. For example,
|
||||
* {@code "secret/myapp"} or {@code "secret/my-application/profile"}.
|
||||
* <p>
|
||||
* Each location will be added to the enclosing {@code Environment} as its own
|
||||
@@ -92,6 +94,15 @@ public @interface VaultPropertySource {
|
||||
*/
|
||||
String propertyNamePrefix() default "";
|
||||
|
||||
/**
|
||||
* Indicate if failure to find the {@link #value() secrets} should be ignored.
|
||||
* <p>
|
||||
* {@literal true} is appropriate if the secrets are completely optional. Default is
|
||||
* {@literal true}.
|
||||
* @since 2.2.
|
||||
*/
|
||||
boolean ignoreSecretNotFound() default true;
|
||||
|
||||
/**
|
||||
* Configure the name of the {@link org.springframework.vault.core.VaultTemplate} bean
|
||||
* to be used with the property sources.
|
||||
|
||||
@@ -124,6 +124,8 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
String ref = propertySource.getString("vaultTemplateRef");
|
||||
String propertyNamePrefix = propertySource.getString("propertyNamePrefix");
|
||||
Renewal renewal = propertySource.getEnum("renewal");
|
||||
boolean ignoreSecretNotFound = propertySource
|
||||
.getBoolean("ignoreSecretNotFound");
|
||||
|
||||
Assert.isTrue(paths.length > 0,
|
||||
"At least one @VaultPropertySource(value) location is required");
|
||||
@@ -143,7 +145,7 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
AbstractBeanDefinition beanDefinition = createBeanDefinition(ref, renewal,
|
||||
propertyTransformer,
|
||||
propertyTransformer, ignoreSecretNotFound,
|
||||
potentiallyResolveRequiredPlaceholders(propertyPath));
|
||||
|
||||
do {
|
||||
@@ -168,7 +170,8 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
private AbstractBeanDefinition createBeanDefinition(String ref, Renewal renewal,
|
||||
PropertyTransformer propertyTransformer, String propertyPath) {
|
||||
PropertyTransformer propertyTransformer, boolean ignoreResourceNotFound,
|
||||
String propertyPath) {
|
||||
|
||||
BeanDefinitionBuilder builder;
|
||||
|
||||
@@ -194,6 +197,7 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
builder.addConstructorArgValue(propertyTransformer);
|
||||
builder.addConstructorArgValue(ignoreResourceNotFound);
|
||||
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
return builder.getBeanDefinition();
|
||||
|
||||
@@ -24,16 +24,17 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
import org.springframework.vault.core.lease.domain.RequestedSecret;
|
||||
import org.springframework.vault.core.lease.event.BeforeSecretLeaseRevocationEvent;
|
||||
import org.springframework.vault.core.lease.event.LeaseListener;
|
||||
import org.springframework.vault.core.lease.event.LeaseListenerAdapter;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretNotFoundEvent;
|
||||
import org.springframework.vault.core.util.PropertyTransformer;
|
||||
import org.springframework.vault.core.util.PropertyTransformers;
|
||||
import org.springframework.vault.support.JsonMapFlattener;
|
||||
@@ -64,7 +65,14 @@ public class LeaseAwareVaultPropertySource
|
||||
|
||||
private final PropertyTransformer propertyTransformer;
|
||||
|
||||
private final LeaseListener leaseListener;
|
||||
private final boolean ignoreSecretNotFound;
|
||||
|
||||
private final LeaseListenerAdapter leaseListener;
|
||||
|
||||
private volatile boolean notFound = false;
|
||||
|
||||
@Nullable
|
||||
private volatile Exception loadError;
|
||||
|
||||
/**
|
||||
* Create a new {@link LeaseAwareVaultPropertySource} given a
|
||||
@@ -111,6 +119,28 @@ public class LeaseAwareVaultPropertySource
|
||||
SecretLeaseContainer secretLeaseContainer, RequestedSecret requestedSecret,
|
||||
PropertyTransformer propertyTransformer) {
|
||||
|
||||
this(name, secretLeaseContainer, requestedSecret, propertyTransformer, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LeaseAwareVaultPropertySource} given a {@code name},
|
||||
* {@link SecretLeaseContainer} and {@link RequestedSecret}. This property source
|
||||
* requests the secret upon initialization and receives secrets once they are emitted
|
||||
* through events published by {@link SecretLeaseContainer}.
|
||||
*
|
||||
* @param name name of the property source, must not be {@literal null}.
|
||||
* @param secretLeaseContainer must not be {@literal null}.
|
||||
* @param requestedSecret must not be {@literal null}.
|
||||
* @param propertyTransformer object to transform properties.
|
||||
* @param ignoreSecretNotFound indicate if failure to find a secret at {@code path}
|
||||
* should be ignored.
|
||||
* @since 2.2
|
||||
* @see PropertyTransformers
|
||||
*/
|
||||
public LeaseAwareVaultPropertySource(String name,
|
||||
SecretLeaseContainer secretLeaseContainer, RequestedSecret requestedSecret,
|
||||
PropertyTransformer propertyTransformer, boolean ignoreSecretNotFound) {
|
||||
|
||||
super(name);
|
||||
|
||||
Assert.notNull(secretLeaseContainer,
|
||||
@@ -122,12 +152,18 @@ public class LeaseAwareVaultPropertySource
|
||||
this.requestedSecret = requestedSecret;
|
||||
this.propertyTransformer = propertyTransformer
|
||||
.andThen(PropertyTransformers.removeNullProperties());
|
||||
this.ignoreSecretNotFound = ignoreSecretNotFound;
|
||||
this.leaseListener = new LeaseListenerAdapter() {
|
||||
@Override
|
||||
public void onLeaseEvent(SecretLeaseEvent leaseEvent) {
|
||||
handleLeaseEvent(leaseEvent,
|
||||
LeaseAwareVaultPropertySource.this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaseError(SecretLeaseEvent leaseEvent, Exception exception) {
|
||||
handleLeaseErrorEvent(leaseEvent, exception);
|
||||
}
|
||||
};
|
||||
|
||||
loadProperties();
|
||||
@@ -144,7 +180,28 @@ public class LeaseAwareVaultPropertySource
|
||||
}
|
||||
|
||||
secretLeaseContainer.addLeaseListener(leaseListener);
|
||||
secretLeaseContainer.addErrorListener(leaseListener);
|
||||
secretLeaseContainer.addRequestedSecret(requestedSecret);
|
||||
|
||||
Exception loadError = this.loadError;
|
||||
if (notFound || loadError != null) {
|
||||
|
||||
String msg = String.format("Vault location [%s] not resolvable",
|
||||
requestedSecret.getPath());
|
||||
|
||||
if (ignoreSecretNotFound) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(String.format("%s: %s", msg,
|
||||
loadError != null ? loadError.getMessage() : "Not found"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (loadError != null) {
|
||||
throw new VaultPropertySourceNotFoundException(msg, loadError);
|
||||
}
|
||||
throw new VaultPropertySourceNotFoundException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RequestedSecret getRequestedSecret() {
|
||||
@@ -180,6 +237,10 @@ public class LeaseAwareVaultPropertySource
|
||||
return;
|
||||
}
|
||||
|
||||
if (leaseEvent instanceof SecretNotFoundEvent) {
|
||||
this.notFound = true;
|
||||
}
|
||||
|
||||
if (leaseEvent instanceof SecretLeaseExpiredEvent
|
||||
|| leaseEvent instanceof BeforeSecretLeaseRevocationEvent
|
||||
|| leaseEvent instanceof SecretLeaseCreatedEvent) {
|
||||
@@ -193,6 +254,22 @@ public class LeaseAwareVaultPropertySource
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook method to handle a {@link SecretLeaseEvent} errors.
|
||||
*
|
||||
* @param leaseEvent must not be {@literal null}.
|
||||
* @param exception offending exception.
|
||||
*/
|
||||
protected void handleLeaseErrorEvent(SecretLeaseEvent leaseEvent,
|
||||
Exception exception) {
|
||||
|
||||
if (leaseEvent.getSource() != getRequestedSecret()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadError = exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook method to transform properties using {@link PropertyTransformer}.
|
||||
*
|
||||
|
||||
@@ -57,6 +57,8 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
|
||||
|
||||
private final PropertyTransformer propertyTransformer;
|
||||
|
||||
private final boolean ignoreSecretNotFound;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
@@ -102,6 +104,27 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
|
||||
*/
|
||||
public VaultPropertySource(String name, VaultOperations vaultOperations, String path,
|
||||
PropertyTransformer propertyTransformer) {
|
||||
this(name, vaultOperations, path, propertyTransformer, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultPropertySource} given a {@code name},
|
||||
* {@link VaultTemplate} and {@code path} inside of Vault. This property source loads
|
||||
* properties upon construction and transforms these by applying
|
||||
* {@link PropertyTransformer}.
|
||||
*
|
||||
* @param name name of the property source, must not be {@literal null}.
|
||||
* @param vaultOperations must not be {@literal null}.
|
||||
* @param path the path inside Vault (e.g. {@code secret/myapp/myproperties}. Must not
|
||||
* be empty or {@literal null}.
|
||||
* @param propertyTransformer object to transform properties.
|
||||
* @param ignoreSecretNotFound indicate if failure to find a secret at {@code path}
|
||||
* should be ignored.
|
||||
* @since 2.2
|
||||
* @see PropertyTransformers
|
||||
*/
|
||||
public VaultPropertySource(String name, VaultOperations vaultOperations, String path,
|
||||
PropertyTransformer propertyTransformer, boolean ignoreSecretNotFound) {
|
||||
|
||||
super(name, vaultOperations);
|
||||
|
||||
@@ -113,6 +136,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
|
||||
this.keyValueDelegate = new KeyValueDelegate(vaultOperations, LinkedHashMap::new);
|
||||
this.propertyTransformer = propertyTransformer
|
||||
.andThen(PropertyTransformers.removeNullProperties());
|
||||
this.ignoreSecretNotFound = ignoreSecretNotFound;
|
||||
|
||||
loadProperties();
|
||||
}
|
||||
@@ -127,9 +151,34 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
|
||||
logger.debug(String.format("Fetching properties from Vault at %s", path));
|
||||
}
|
||||
|
||||
Map<String, Object> properties = doGetProperties(path);
|
||||
Map<String, Object> properties = null;
|
||||
RuntimeException error = null;
|
||||
|
||||
if (properties != null) {
|
||||
try {
|
||||
properties = doGetProperties(path);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (properties == null) {
|
||||
|
||||
String msg = String.format("Vault location [%s] not resolvable", path);
|
||||
|
||||
if (ignoreSecretNotFound) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(String.format("%s: %s", msg,
|
||||
error != null ? error.getMessage() : "Not found"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (error != null) {
|
||||
throw new VaultPropertySourceNotFoundException(msg, error);
|
||||
}
|
||||
throw new VaultPropertySourceNotFoundException(msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.properties.putAll(doTransformProperties(properties));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2019 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.vault.core.env;
|
||||
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.annotation.VaultPropertySource;
|
||||
|
||||
/**
|
||||
* Exception throws when a {@code VaultPropertySource} could not load its properties.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.2
|
||||
* @see VaultPropertySource#ignoreSecretNotFound()
|
||||
*/
|
||||
public class VaultPropertySourceNotFoundException extends VaultException {
|
||||
|
||||
/**
|
||||
* Create a {@code VaultPropertySourceNotFoundException} with the specified detail
|
||||
* message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public VaultPropertySourceNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code VaultPropertySourceNotFoundException} with the specified detail
|
||||
* message and nested exception.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
* @param cause the nested exception.
|
||||
*/
|
||||
public VaultPropertySourceNotFoundException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -653,12 +653,20 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher
|
||||
RequestedSecret requestedSecret) {
|
||||
|
||||
try {
|
||||
VaultResponseSupport<Map<String, Object>> secrets;
|
||||
|
||||
if (keyValueDelegate.isVersioned(requestedSecret.getPath())) {
|
||||
return keyValueDelegate.getSecret(requestedSecret.getPath());
|
||||
secrets = keyValueDelegate.getSecret(requestedSecret.getPath());
|
||||
}
|
||||
else {
|
||||
secrets = this.operations.read(requestedSecret.getPath());
|
||||
}
|
||||
|
||||
return this.operations.read(requestedSecret.getPath());
|
||||
if (secrets == null) {
|
||||
onSecretsNotFound(requestedSecret);
|
||||
}
|
||||
|
||||
return secrets;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseErrorEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretNotFoundEvent;
|
||||
|
||||
/**
|
||||
* Publisher for {@link SecretLeaseEvent}s.
|
||||
@@ -121,6 +122,17 @@ public class SecretLeaseEventPublisher implements InitializingBean {
|
||||
dispatch(new SecretLeaseCreatedEvent(requestedSecret, lease, body));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook method called when secrets were not found. The default implementation is to
|
||||
* notify {@link LeaseListener}. Implementations can override this method in
|
||||
* subclasses.
|
||||
*
|
||||
* @param requestedSecret must not be {@literal null}.
|
||||
*/
|
||||
protected void onSecretsNotFound(RequestedSecret requestedSecret) {
|
||||
dispatch(new SecretNotFoundEvent(requestedSecret, Lease.none()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook method called when a {@link Lease} is renewed. The default implementation is
|
||||
* to notify {@link LeaseListener}. Implementations can override this method in
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2019 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.vault.core.lease.event;
|
||||
|
||||
import org.springframework.vault.core.lease.domain.Lease;
|
||||
import org.springframework.vault.core.lease.domain.RequestedSecret;
|
||||
|
||||
/**
|
||||
* Event published after secrets could not be found for a {@link RequestedSecret}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.2
|
||||
*/
|
||||
public class SecretNotFoundEvent extends SecretLeaseEvent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create a new {@link SecretNotFoundEvent} given {@link RequestedSecret}
|
||||
*
|
||||
* @param requestedSecret must not be {@literal null}.
|
||||
* @param lease must not be {@literal null}.
|
||||
*/
|
||||
public SecretNotFoundEvent(RequestedSecret requestedSecret, Lease lease) {
|
||||
super(requestedSecret, lease);
|
||||
}
|
||||
}
|
||||
@@ -21,27 +21,26 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.vault.annotation.VaultPropertySource.Renewal;
|
||||
import org.springframework.vault.core.VaultIntegrationTestConfiguration;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.core.env.VaultPropertySourceNotFoundException;
|
||||
import org.springframework.vault.util.VaultExtension;
|
||||
import org.springframework.vault.util.VaultInitializer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Integration test for {@link VaultPropertySource}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ExtendWith(VaultExtension.class)
|
||||
@ContextConfiguration
|
||||
class LeaseAwareVaultPropertySourceIntegrationTests {
|
||||
|
||||
@VaultPropertySource(value = { "secret/myapp",
|
||||
@@ -49,11 +48,15 @@ class LeaseAwareVaultPropertySourceIntegrationTests {
|
||||
static class Config extends VaultIntegrationTestConfiguration {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
Environment env;
|
||||
@VaultPropertySource(value = { "unknown" }, ignoreSecretNotFound = false)
|
||||
static class FailingConfig extends VaultIntegrationTestConfiguration {
|
||||
}
|
||||
|
||||
@Value("${myapp}")
|
||||
String myapp;
|
||||
@VaultPropertySource(value = {
|
||||
"unknown" }, ignoreSecretNotFound = false, renewal = Renewal.RENEW)
|
||||
static class FailingRenewableConfig extends VaultIntegrationTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void beforeClass(VaultInitializer vaultInitializer) {
|
||||
@@ -67,14 +70,51 @@ class LeaseAwareVaultPropertySourceIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void environmentShouldResolveProperties() {
|
||||
void shouldLoadProperties() {
|
||||
|
||||
assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
|
||||
assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class, PropertyConsumer.class)) {
|
||||
ConfigurableEnvironment env = context.getEnvironment();
|
||||
PropertyConsumer consumer = context.getBean(PropertyConsumer.class);
|
||||
|
||||
assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
|
||||
assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
|
||||
assertThat(consumer.myapp).isEqualTo("myvalue");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueShouldInjectProperty() {
|
||||
assertThat(myapp).isEqualTo("myvalue");
|
||||
void shouldFailIfPropertiesNotFound() {
|
||||
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
FailingConfig.class)) {
|
||||
fail("AnnotationConfigApplicationContext startup did not fail");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e)
|
||||
.hasRootCauseInstanceOf(VaultPropertySourceNotFoundException.class)
|
||||
.hasMessageContaining("Vault location [unknown] not resolvable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfRenewablePropertiesNotFound() {
|
||||
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
FailingRenewableConfig.class)) {
|
||||
|
||||
fail("AnnotationConfigApplicationContext startup did not fail");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e)
|
||||
.hasRootCauseInstanceOf(VaultPropertySourceNotFoundException.class)
|
||||
.hasMessageContaining("Vault location [unknown] not resolvable");
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class PropertyConsumer {
|
||||
@Value("${myapp}")
|
||||
String myapp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ class VaultPropertySourceUnitTests {
|
||||
SecretLeaseContainer leaseContainerMock = ctx.getBean(SecretLeaseContainer.class);
|
||||
verify(leaseContainerMock).afterPropertiesSet();
|
||||
verify(leaseContainerMock).addLeaseListener(any());
|
||||
verify(leaseContainerMock).addErrorListener(any());
|
||||
verify(leaseContainerMock)
|
||||
.addRequestedSecret(RequestedSecret.renewable("foo/renewable"));
|
||||
verifyNoMoreInteractions(leaseContainerMock);
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2019 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.vault.core.env;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
import org.springframework.vault.core.lease.domain.Lease;
|
||||
import org.springframework.vault.core.lease.domain.RequestedSecret;
|
||||
import org.springframework.vault.core.lease.event.LeaseErrorListener;
|
||||
import org.springframework.vault.core.lease.event.LeaseListener;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseErrorEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretNotFoundEvent;
|
||||
import org.springframework.vault.core.util.PropertyTransformers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LeaseAwareVaultPropertySource}.
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LeaseAwareVaultPropertySourceUnitTests {
|
||||
|
||||
@Mock
|
||||
SecretLeaseContainer leaseContainer;
|
||||
|
||||
@Test
|
||||
void shouldLoadProperties() {
|
||||
|
||||
RequestedSecret secret = RequestedSecret.renewable("my-path");
|
||||
|
||||
List<LeaseListener> listeners = new ArrayList<>();
|
||||
doAnswer(invocation -> {
|
||||
listeners.add(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(leaseContainer).addLeaseListener(any());
|
||||
when(leaseContainer.addRequestedSecret(any())).then(invocation -> {
|
||||
|
||||
listeners.forEach(leaseListener -> leaseListener
|
||||
.onLeaseEvent(new SecretLeaseCreatedEvent(invocation.getArgument(0),
|
||||
Lease.none(), Collections.singletonMap("key", "value"))));
|
||||
return invocation.getArgument(0);
|
||||
});
|
||||
|
||||
LeaseAwareVaultPropertySource propertySource = new LeaseAwareVaultPropertySource(
|
||||
leaseContainer, secret);
|
||||
|
||||
assertThat(propertySource.getPropertyNames()).containsOnly("key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoresNotFoundByDefault() {
|
||||
|
||||
RequestedSecret secret = RequestedSecret.renewable("my-path");
|
||||
|
||||
List<LeaseListener> listeners = new ArrayList<>();
|
||||
doAnswer(invocation -> {
|
||||
listeners.add(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(leaseContainer).addLeaseListener(any());
|
||||
when(leaseContainer.addRequestedSecret(any())).then(invocation -> {
|
||||
|
||||
listeners.forEach(leaseListener -> leaseListener.onLeaseEvent(
|
||||
new SecretNotFoundEvent(invocation.getArgument(0), Lease.none())));
|
||||
return invocation.getArgument(0);
|
||||
});
|
||||
|
||||
LeaseAwareVaultPropertySource propertySource = new LeaseAwareVaultPropertySource(
|
||||
leaseContainer, secret);
|
||||
|
||||
assertThat(propertySource.getPropertyNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoresErrorsByDefault() {
|
||||
|
||||
RequestedSecret secret = RequestedSecret.renewable("my-path");
|
||||
|
||||
List<LeaseErrorListener> errorListeners = new ArrayList<>();
|
||||
doAnswer(invocation -> {
|
||||
errorListeners.add(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(leaseContainer).addErrorListener(any());
|
||||
when(leaseContainer.addRequestedSecret(any())).then(invocation -> {
|
||||
|
||||
errorListeners.forEach(leaseListener -> {
|
||||
RuntimeException exception = new RuntimeException("Backend error");
|
||||
leaseListener.onLeaseError(
|
||||
new SecretLeaseErrorEvent(secret, Lease.none(), exception),
|
||||
exception);
|
||||
});
|
||||
return invocation.getArgument(0);
|
||||
});
|
||||
|
||||
LeaseAwareVaultPropertySource propertySource = new LeaseAwareVaultPropertySource(
|
||||
leaseContainer, secret);
|
||||
|
||||
assertThat(propertySource.getPropertyNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void propagatesErrorIfIgnoreResourceNotFoundIsFalse() {
|
||||
|
||||
RequestedSecret secret = RequestedSecret.renewable("my-path");
|
||||
|
||||
List<LeaseErrorListener> errorListeners = new ArrayList<>();
|
||||
doAnswer(invocation -> {
|
||||
errorListeners.add(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(leaseContainer).addErrorListener(any());
|
||||
when(leaseContainer.addRequestedSecret(any())).then(invocation -> {
|
||||
|
||||
errorListeners.forEach(leaseListener -> {
|
||||
RuntimeException exception = new RuntimeException("Backend error");
|
||||
leaseListener.onLeaseError(
|
||||
new SecretLeaseErrorEvent(secret, Lease.none(), exception),
|
||||
exception);
|
||||
});
|
||||
return invocation.getArgument(0);
|
||||
});
|
||||
|
||||
assertThatThrownBy(() -> new LeaseAwareVaultPropertySource("name", leaseContainer,
|
||||
secret, PropertyTransformers.noop(), false))
|
||||
.isInstanceOf(VaultPropertySourceNotFoundException.class)
|
||||
.hasRootCauseExactlyInstanceOf(RuntimeException.class);
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,14 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.vault.core.util.PropertyTransformers;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -58,6 +60,47 @@ class VaultPropertySourceUnitTests {
|
||||
"/secret", PropertyTransformers.noop()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertiesNotFoundShouldFailOnIgnoreSecretNotFoundDisabled() {
|
||||
|
||||
assertThatThrownBy(() -> new VaultPropertySource("hello", vaultTemplate,
|
||||
"secret/myapp", PropertyTransformers.noop(), false))
|
||||
.isInstanceOf(VaultPropertySourceNotFoundException.class)
|
||||
.hasNoCause();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPropagateFetchErrorIgnoreSecretNotFoundDisabled() {
|
||||
|
||||
when(vaultTemplate.read("secret/myapp"))
|
||||
.thenThrow(new VaultException("HTTP error"));
|
||||
assertThatThrownBy(() -> new VaultPropertySource("hello", vaultTemplate,
|
||||
"secret/myapp", PropertyTransformers.noop(), false))
|
||||
.isInstanceOf(VaultPropertySourceNotFoundException.class)
|
||||
.hasRootCauseExactlyInstanceOf(VaultException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertiesNotFoundShouldBeIgnoredByDefault() {
|
||||
|
||||
VaultPropertySource source = new VaultPropertySource("hello", vaultTemplate,
|
||||
"secret/myapp", PropertyTransformers.noop());
|
||||
|
||||
assertThat(source.getPropertyNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreFetchErrorByDefault() {
|
||||
|
||||
when(vaultTemplate.read("secret/myapp"))
|
||||
.thenThrow(new VaultException("HTTP error"));
|
||||
|
||||
VaultPropertySource source = new VaultPropertySource("hello", vaultTemplate,
|
||||
"secret/myapp", PropertyTransformers.noop());
|
||||
|
||||
assertThat(source.getPropertyNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoadProperties() {
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.vault.annotation.VaultPropertySource;
|
||||
import org.springframework.vault.core.VaultIntegrationTestConfiguration;
|
||||
import org.springframework.vault.core.VaultKeyValueOperations;
|
||||
@@ -52,6 +53,7 @@ class VersionedKeyValueBackendIntegrationTests extends IntegrationTestSupport {
|
||||
}
|
||||
|
||||
@VaultPropertySource(value = "versioned/my/path", renewal = VaultPropertySource.Renewal.ROTATE)
|
||||
@PropertySource(value = "http://foo", ignoreResourceNotFound = true)
|
||||
@Configuration
|
||||
static class RotatingSecret {
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.vault.core.lease.event.LeaseListenerAdapter;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent;
|
||||
import org.springframework.vault.core.lease.event.SecretNotFoundEvent;
|
||||
import org.springframework.vault.support.LeaseStrategy;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
@@ -127,7 +128,8 @@ class SecretLeaseContainerUnitTests {
|
||||
|
||||
secretLeaseContainer.requestRenewableSecret(requestedSecret.getPath());
|
||||
|
||||
verifyZeroInteractions(leaseListenerAdapter);
|
||||
verify(leaseListenerAdapter).onLeaseEvent(any(SecretNotFoundEvent.class));
|
||||
verifyNoMoreInteractions(leaseListenerAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -158,7 +160,7 @@ class SecretLeaseContainerUnitTests {
|
||||
VaultResponse secrets = new VaultResponse();
|
||||
secrets.setLeaseId("lease");
|
||||
secrets.setRenewable(false);
|
||||
secrets.setData(Collections.singletonMap("key", (Object) "value"));
|
||||
secrets.setData(Collections.singletonMap("key", "value"));
|
||||
|
||||
when(vaultOperations.read(requestedSecret.getPath())).thenReturn(secrets);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user