Consistently resolve property placeholders in VaultPropertySource path.
We now resolve property placeholders during configuration class parsing using Environment.resolveRequiredPlaceholders(…). Previously, only placeholders in non-renewable property sources were resolved as they were part of BeanDefinition. Rotating/renewable property sources used a scalar RequestedSecret object which did not participate in bean definition. Resolves gh-420.
This commit is contained in:
@@ -28,9 +28,11 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
@@ -55,7 +57,14 @@ import org.springframework.vault.core.util.PropertyTransformers;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
BeanFactoryPostProcessor {
|
||||
BeanFactoryPostProcessor, EnvironmentAware {
|
||||
|
||||
private @Nullable Environment environment;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
@@ -82,7 +91,7 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
|
||||
for (PropertySource<?> vaultPropertySource : propertySources) {
|
||||
|
||||
if (propertySources.contains(vaultPropertySource.getName())) {
|
||||
if (mutablePropertySources.contains(vaultPropertySource.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -135,7 +144,8 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
AbstractBeanDefinition beanDefinition = createBeanDefinition(ref,
|
||||
renewal, propertyTransformer, propertyPath);
|
||||
renewal, propertyTransformer,
|
||||
potentiallyResolveRequiredPlaceholders(propertyPath));
|
||||
|
||||
do {
|
||||
String beanName = "vaultPropertySource#" + counter;
|
||||
@@ -152,6 +162,11 @@ class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
}
|
||||
|
||||
private String potentiallyResolveRequiredPlaceholders(String expression) {
|
||||
return this.environment != null ? this.environment
|
||||
.resolveRequiredPlaceholders(expression) : expression;
|
||||
}
|
||||
|
||||
private AbstractBeanDefinition createBeanDefinition(String ref, Renewal renewal,
|
||||
PropertyTransformer propertyTransformer, String propertyPath) {
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.vault.annotation;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -23,10 +24,14 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.vault.core.lease.SecretLeaseContainer;
|
||||
import org.springframework.vault.core.lease.domain.RequestedSecret;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.springframework.vault.annotation.VaultPropertySource.Renewal.RENEW;
|
||||
|
||||
/**
|
||||
* Unit test for {@link VaultPropertySource}.
|
||||
@@ -42,6 +47,11 @@ public class VaultPropertySourceUnitTests {
|
||||
VaultTemplate vaultTemplate() {
|
||||
return Mockito.mock(VaultTemplate.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecretLeaseContainer secretLeaseContainer() {
|
||||
return Mockito.mock(SecretLeaseContainer.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -56,6 +66,21 @@ public class VaultPropertySourceUnitTests {
|
||||
static class DevProfile {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@VaultPropertySource("foo/${my_property}")
|
||||
static class NonRenewableConfig {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@VaultPropertySource(value = "foo/${my_property}", renewal = RENEW)
|
||||
static class RenewableConfig {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
System.clearProperty("my_property");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotEnablePropertySource() {
|
||||
|
||||
@@ -91,4 +116,41 @@ public class VaultPropertySourceUnitTests {
|
||||
verify(templateMock).read("foo");
|
||||
verify(templateMock, never()).read("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResolvePlaceholderForNonRenewablePropertySource() {
|
||||
|
||||
System.setProperty("my_property", "non-renewable");
|
||||
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
|
||||
ctx.register(Config.class);
|
||||
ctx.register(NonRenewableConfig.class);
|
||||
ctx.refresh();
|
||||
|
||||
VaultTemplate templateMock = ctx.getBean(VaultTemplate.class);
|
||||
|
||||
verify(templateMock).afterPropertiesSet();
|
||||
verify(templateMock).read("foo/non-renewable");
|
||||
verifyNoMoreInteractions(templateMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResolvePlaceholderForRenewablePropertySource() throws Exception {
|
||||
|
||||
System.setProperty("my_property", "renewable");
|
||||
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
|
||||
ctx.register(Config.class);
|
||||
ctx.register(RenewableConfig.class);
|
||||
ctx.refresh();
|
||||
|
||||
SecretLeaseContainer leaseContainerMock = ctx.getBean(SecretLeaseContainer.class);
|
||||
verify(leaseContainerMock).afterPropertiesSet();
|
||||
verify(leaseContainerMock).addLeaseListener(any());
|
||||
verify(leaseContainerMock).addRequestedSecret(
|
||||
RequestedSecret.renewable("foo/renewable"));
|
||||
verifyNoMoreInteractions(leaseContainerMock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,27 @@ public class AppConfig {
|
||||
|
||||
NOTE: Secrets obtained from `generic` secret backends are associated with a TTL (`refresh_interval`) but not a lease Id. Spring Vault's ``PropertySource`` rotates generic secrets when reaching its TTL.
|
||||
|
||||
|
||||
Any `${…}` placeholders present in a `@VaultPropertySource` path are resolved against the set of property sources already registered against the environment, as the following example shows:
|
||||
|
||||
|
||||
.Declaring a `@VaultPropertySource` path using placeholders
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@VaultPropertySource(value = "aws/creds/${my.placeholder:fallback/value}",
|
||||
propertyNamePrefix = "aws.",
|
||||
renewal = Renewal.ROTATE)
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Assuming that `my.placeholder` is present in one of the property sources already registered (for example, system properties or environment variables), the placeholder is resolved to the corresponding value.
|
||||
If not, then `fallback/value` is used as a default.
|
||||
If no default is specified and a property cannot be resolved, an `IllegalArgumentException` is thrown.
|
||||
|
||||
In certain situations, it may not be possible or practical to tightly control
|
||||
property source ordering when using `@VaultPropertySource` annotations.
|
||||
For example, if the `@Configuration` classes above were registered via
|
||||
|
||||
Reference in New Issue
Block a user