diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java index 5423386b..92585442 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java @@ -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 { + implements ApplicationContextInitializer, 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 propertySourceLocators = new ArrayList<>(); + @Override + public int getOrder() { + return this.order; + } + public void setPropertySourceLocators( Collection propertySourceLocators) { this.propertySourceLocators = new ArrayList<>(propertySourceLocators); diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java index c829247f..fafa9f1d 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java @@ -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 found = new LinkedHashSet<>(); + Map 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 decrypt(PropertySource source) { + Map overrides = new LinkedHashMap(); + decrypt(source, overrides); + return overrides; + } + private void decrypt(PropertySource source, Map 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 } } - + } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java index 74a4a66c..81240eec 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java @@ -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 diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java new file mode 100644 index 00000000..9bc8e28e --- /dev/null +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java @@ -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 MAP = new HashMap( + Collections. singletonMap("custom.foo", "{cipher}6154ca04d4bb6144d672c4e3d750b5147116dd381946d51fa44f8bc25dc256f4")); + + @Override + public PropertySource locate(Environment environment) { + return new MapPropertySource("testBootstrap", MAP); + } + + } + +} diff --git a/spring-cloud-context/src/test/resources/custom.properties b/spring-cloud-context/src/test/resources/custom.properties new file mode 100644 index 00000000..1ee3320d --- /dev/null +++ b/spring-cloud-context/src/test/resources/custom.properties @@ -0,0 +1 @@ +spring.main.sources: org.springframework.cloud.bootstrap.BootstrapOrderingCustomPropertySourceIntegrationTests.PropertySourceConfiguration