Add another property source for decrypted bootstrap properties
The problem is that we do want bootstrap properties to override default properties in most cases, but if they are decrypted those values have to be added with the highest possible precedence. Fixes gh-54
This commit is contained in:
@@ -38,6 +38,7 @@ import org.springframework.cloud.logging.LoggingRebinder;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
@@ -53,16 +54,23 @@ import org.springframework.util.ResourceUtils;
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
|
||||
public class PropertySourceBootstrapConfiguration
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
|
||||
|
||||
private static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;
|
||||
|
||||
private static Log logger = LogFactory
|
||||
.getLog(PropertySourceBootstrapConfiguration.class);
|
||||
|
||||
private int order = Ordered.HIGHEST_PRECEDENCE + 10;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setPropertySourceLocators(
|
||||
Collection<PropertySourceLocator> propertySourceLocators) {
|
||||
this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.springframework.cloud.bootstrap.encrypt;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -38,7 +40,7 @@ import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
/**
|
||||
* Decrypt properties from the environment and insert them with high priority so they
|
||||
* override the encrypted values.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@@ -47,6 +49,8 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
|
||||
public static final String DECRYPTED_PROPERTY_SOURCE_NAME = "decrypted";
|
||||
|
||||
public static final String DECRYPTED_BOOTSTRAP_PROPERTY_SOURCE_NAME = "decryptedBootstrap";
|
||||
|
||||
private int order = Ordered.HIGHEST_PRECEDENCE + 15;
|
||||
|
||||
private static Log logger = LogFactory
|
||||
@@ -58,7 +62,7 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
|
||||
/**
|
||||
* Strategy to determine how to handle exceptions during decryption.
|
||||
*
|
||||
*
|
||||
* @param failOnError the flag value (default true)
|
||||
*/
|
||||
public void setFailOnError(boolean failOnError) {
|
||||
@@ -71,28 +75,54 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
return this.order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
MapPropertySource decrypted = new MapPropertySource(
|
||||
DECRYPTED_PROPERTY_SOURCE_NAME, decrypt(environment.getPropertySources()));
|
||||
if (!decrypted.getSource().isEmpty()) {
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
|
||||
Set<String> found = new LinkedHashSet<>();
|
||||
Map<String, Object> map = decrypt(propertySources);
|
||||
if (!map.isEmpty()) {
|
||||
// We have some decrypted properties
|
||||
insert(environment.getPropertySources(), decrypted);
|
||||
found.addAll(map.keySet());
|
||||
insert(applicationContext,
|
||||
new MapPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map));
|
||||
}
|
||||
PropertySource<?> bootstrap = propertySources
|
||||
.get(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME);
|
||||
if (bootstrap != null) {
|
||||
map = decrypt(bootstrap);
|
||||
if (!map.isEmpty()) {
|
||||
found.addAll(map.keySet());
|
||||
insert(applicationContext, new MapPropertySource(
|
||||
DECRYPTED_BOOTSTRAP_PROPERTY_SOURCE_NAME, map));
|
||||
}
|
||||
}
|
||||
if (!found.isEmpty()) {
|
||||
ApplicationContext parent = applicationContext.getParent();
|
||||
if (parent != null
|
||||
&& (parent.getEnvironment() instanceof ConfigurableEnvironment)) {
|
||||
ConfigurableEnvironment mutable = (ConfigurableEnvironment) parent
|
||||
.getEnvironment();
|
||||
if (parent != null) {
|
||||
// The parent is actually the bootstrap context, and it is fully
|
||||
// initialized, so we can fire an EnvironmentChangeEvent there to rebind
|
||||
// @ConfigurationProperties, in case they were encrypted.
|
||||
insert(mutable.getPropertySources(), decrypted);
|
||||
parent.publishEvent(new EnvironmentChangeEvent(decrypted.getSource()
|
||||
.keySet()));
|
||||
parent.publishEvent(new EnvironmentChangeEvent(found));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(ApplicationContext applicationContext,
|
||||
MapPropertySource propertySource) {
|
||||
ApplicationContext parent = applicationContext;
|
||||
while (parent != null) {
|
||||
if (parent.getEnvironment() instanceof ConfigurableEnvironment) {
|
||||
ConfigurableEnvironment mutable = (ConfigurableEnvironment) parent
|
||||
.getEnvironment();
|
||||
insert(mutable.getPropertySources(), propertySource);
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,9 +131,17 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
MapPropertySource propertySource) {
|
||||
if (propertySources
|
||||
.contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
|
||||
propertySources.addAfter(
|
||||
BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME,
|
||||
propertySource);
|
||||
if (DECRYPTED_BOOTSTRAP_PROPERTY_SOURCE_NAME
|
||||
.equals(propertySource.getName())) {
|
||||
propertySources.addBefore(
|
||||
BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME,
|
||||
propertySource);
|
||||
}
|
||||
else {
|
||||
propertySources.addAfter(
|
||||
BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME,
|
||||
propertySource);
|
||||
}
|
||||
}
|
||||
else {
|
||||
propertySources.addFirst(propertySource);
|
||||
@@ -118,6 +156,12 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
return overrides;
|
||||
}
|
||||
|
||||
private Map<String, Object> decrypt(PropertySource<?> source) {
|
||||
Map<String, Object> overrides = new LinkedHashMap<String, Object>();
|
||||
decrypt(source, overrides);
|
||||
return overrides;
|
||||
}
|
||||
|
||||
private void decrypt(PropertySource<?> source, Map<String, Object> overrides) {
|
||||
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
@@ -130,18 +174,20 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
if (value.startsWith("{cipher}")) {
|
||||
value = value.substring("{cipher}".length());
|
||||
try {
|
||||
value = encryptor.decrypt(value);
|
||||
value = this.encryptor.decrypt(value);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Decrypted: key=" + key);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
String message = "Cannot decrypt: key=" + key;
|
||||
if (failOnError) {
|
||||
if (this.failOnError) {
|
||||
throw new IllegalStateException(message, e);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn(message, e);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
logger.warn(message);
|
||||
}
|
||||
// Set value to empty to avoid making a password out of the
|
||||
@@ -164,5 +210,5 @@ public class EnvironmentDecryptApplicationInitializer implements
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@IntegrationTest("encrypt.key:deadbeef")
|
||||
@ActiveProfiles("encrypt")
|
||||
public class BootstrapOrderingAutoConfigurationIntegrationTests {
|
||||
|
||||
|
||||
@Autowired
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
@Test
|
||||
public void bootstrapPropertiesExist() {
|
||||
assertTrue(environment.getPropertySources().contains("bootstrap"));
|
||||
assertTrue(this.environment.getPropertySources().contains("bootstrap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalPropertiesDecrypted() {
|
||||
assertEquals("foo", environment.resolvePlaceholders("${foo}"));
|
||||
assertEquals("foo", this.environment.resolvePlaceholders("${foo}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bootstrapPropertiesDecrypted() {
|
||||
assertEquals("bar", environment.resolvePlaceholders("${bar}"));
|
||||
assertEquals("bar", this.environment.resolvePlaceholders("${bar}"));
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.cloud.bootstrap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.bootstrap.BootstrapOrderingCustomPropertySourceIntegrationTests.Application;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = Application.class)
|
||||
@IntegrationTest({"encrypt.key:deadbeef", "spring.cloud.bootstrap.name:custom"})
|
||||
@ActiveProfiles("encrypt")
|
||||
public class BootstrapOrderingCustomPropertySourceIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
@Test
|
||||
public void bootstrapPropertiesExist() {
|
||||
assertTrue(this.environment.getPropertySources().contains("bootstrap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPropertiesDecrypted() {
|
||||
assertEquals("bar", this.environment.resolvePlaceholders("${custom.foo}"));
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration
|
||||
protected static class Application {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
// This is added to bootstrap context as a source in bootstrap.properties
|
||||
protected static class PropertySourceConfiguration implements PropertySourceLocator {
|
||||
|
||||
public static Map<String, Object> MAP = new HashMap<String, Object>(
|
||||
Collections.<String, Object> singletonMap("custom.foo", "{cipher}6154ca04d4bb6144d672c4e3d750b5147116dd381946d51fa44f8bc25dc256f4"));
|
||||
|
||||
@Override
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
return new MapPropertySource("testBootstrap", MAP);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spring.main.sources: org.springframework.cloud.bootstrap.BootstrapOrderingCustomPropertySourceIntegrationTests.PropertySourceConfiguration
|
||||
Reference in New Issue
Block a user