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:
Dave Syer
2015-10-02 14:54:50 +01:00
parent 41f0b641dd
commit a6124e7063
5 changed files with 147 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
spring.main.sources: org.springframework.cloud.bootstrap.BootstrapOrderingCustomPropertySourceIntegrationTests.PropertySourceConfiguration