From 988189db82d136ce704180bf3604f7992e2df919 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 11 Nov 2015 09:13:37 +0000 Subject: [PATCH] Add extra decryption step before propery source locators See also the integration tests in spring-cloud-samples/tests Fixes gh-58 --- ...onPropertiesRebinderAutoConfiguration.java | 82 +++++++++++++++++++ .../RefreshAutoConfiguration.java | 57 ------------- .../BootstrapApplicationListener.java | 75 +++++++++++++---- ...ationPropertiesBootstrapConfiguration.java | 52 ------------ ...ironmentDecryptApplicationInitializer.java | 4 + .../main/resources/META-INF/spring.factories | 3 +- ...ionPropertiesRebinderIntegrationTests.java | 3 +- ...ropertiesRebinderListIntegrationTests.java | 3 +- 8 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java delete mode 100644 spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java new file mode 100644 index 00000000..fae23c21 --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2015 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 + * + * http://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.cloud.autoconfigure; + +import java.util.Collections; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; +import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor; +import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class) +public class ConfigurationPropertiesRebinderAutoConfiguration + implements ApplicationContextAware, SmartInitializingSingleton { + + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.context = applicationContext; + } + + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + public ConfigurationPropertiesBeans configurationPropertiesBeans() { + // Since this is a BeanPostProcessor we have to be super careful not to + // cause a cascade of bean instantiation. Knowing the *name* of the beans we + // need is super optimal, but a little brittle (unfortunately we have no + // choice). + ConfigurationBeanFactoryMetaData metaData = this.context.getBean( + ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME + + ".store", + ConfigurationBeanFactoryMetaData.class); + ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans(); + beans.setBeanMetaDataStore(metaData); + return beans; + } + + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + public ConfigurationPropertiesRebinder configurationPropertiesRebinder( + ConfigurationPropertiesBeans beans, + ConfigurationPropertiesBindingPostProcessor binder) { + ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder( + binder, beans); + return rebinder; + } + + @Override + public void afterSingletonsInstantiated() { + // After all beans are initialized send a pre-emptive EnvironmentChangeEvent + // so that anything that needs to rebind gets a chance now (especially for + // beans in the parent context) + this.context.publishEvent( + new EnvironmentChangeEvent(Collections. emptySet())); + } +} \ No newline at end of file diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java index bcdcc99a..9ac6b559 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java @@ -17,12 +17,10 @@ package org.springframework.cloud.autoconfigure; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; @@ -34,23 +32,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor; -import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar; import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.environment.EnvironmentManager; -import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; -import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.cloud.context.restart.RestartEndpoint; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.endpoint.RefreshEndpoint; import org.springframework.cloud.logging.LoggingRebinder; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; @@ -138,53 +128,6 @@ public class RefreshAutoConfiguration { } - @Configuration - @ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class) - protected static class ConfigurationPropertiesRebinderConfiguration implements - ApplicationContextAware, SmartInitializingSingleton { - - private ApplicationContext context; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - this.context = applicationContext; - } - - @Bean - @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) - public ConfigurationPropertiesBeans configurationPropertiesBeans() { - // Since this is a BeanPostProcessor we have to be super careful not to - // cause a cascade of bean instantiation. Knowing the *name* of the beans we - // need is super optimal, but a little brittle (unfortunately we have no - // choice). - ConfigurationBeanFactoryMetaData metaData = this.context.getBean( - ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME - + ".store", ConfigurationBeanFactoryMetaData.class); - ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans(); - beans.setBeanMetaDataStore(metaData); - return beans; - } - - @Bean - @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) - public ConfigurationPropertiesRebinder configurationPropertiesRebinder( - ConfigurationPropertiesBeans beans, - ConfigurationPropertiesBindingPostProcessor binder) { - ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder( - binder, beans); - return rebinder; - } - - @Override - public void afterSingletonsInstantiated() { - // After all beans are initialized send a pre-emptive EnvironmentChangeEvent - // so that anything that needs to rebind gets a chance now (especially for - // beans in the parent context) - this.context.publishEvent(new EnvironmentChangeEvent(Collections - . emptySet())); - } - } - @ConditionalOnClass(Endpoint.class) protected static class RefreshEndpointsConfiguration { diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java index 2c41f435..0d557772 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java @@ -23,15 +23,18 @@ import java.util.List; import java.util.Map; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.boot.Banner.Mode; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; @@ -52,8 +55,8 @@ import org.springframework.util.StringUtils; * @author Dave Syer * */ -public class BootstrapApplicationListener implements - ApplicationListener, Ordered { +public class BootstrapApplicationListener + implements ApplicationListener, Ordered { public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap"; @@ -94,8 +97,8 @@ public class BootstrapApplicationListener implements if (StringUtils.hasText(configLocation)) { bootstrapMap.put("spring.config.location", configLocation); } - bootstrapProperties.addFirst(new MapPropertySource( - BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap)); + bootstrapProperties.addFirst( + new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap)); bootstrapProperties.addFirst(new MapPropertySource("bootstrapInProgress", Collections. emptyMap())); for (PropertySource source : environment.getPropertySources()) { @@ -103,11 +106,11 @@ public class BootstrapApplicationListener implements } ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates - List names = SpringFactoriesLoader.loadFactoryNames( - BootstrapConfiguration.class, classLoader); + List names = SpringFactoriesLoader + .loadFactoryNames(BootstrapConfiguration.class, classLoader); // TODO: is it possible or sensible to share a ResourceLoader? SpringApplicationBuilder builder = new SpringApplicationBuilder() - .profiles(environment.getActiveProfiles()).showBanner(false) + .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF) .environment(bootstrapEnvironment) .properties("spring.application.name:" + configName).web(false); List> sources = new ArrayList<>(); @@ -133,7 +136,8 @@ public class BootstrapApplicationListener implements private void addAncestorInitializer(SpringApplication application, ConfigurableApplicationContext context) { boolean installed = false; - for (ApplicationContextInitializer initializer : application.getInitializers()) { + for (ApplicationContextInitializer initializer : application + .getInitializers()) { if (initializer instanceof AncestorInitializer) { installed = true; // New parent @@ -154,9 +158,27 @@ public class BootstrapApplicationListener implements ApplicationContextInitializer.class); application.addInitializers(initializers .toArray(new ApplicationContextInitializer[initializers.size()])); + addBootstrapDecryptInitializer(application); } - private List getOrderedBeansOfType(ListableBeanFactory context, Class type) { + private void addBootstrapDecryptInitializer(SpringApplication application) { + DelegatingEnvironmentDecryptApplicationInitializer decrypter = null; + for (ApplicationContextInitializer initializer : application + .getInitializers()) { + if (initializer instanceof EnvironmentDecryptApplicationInitializer) { + @SuppressWarnings("unchecked") + ApplicationContextInitializer delegate = (ApplicationContextInitializer) initializer; + decrypter = new DelegatingEnvironmentDecryptApplicationInitializer( + delegate); + } + } + if (decrypter != null) { + application.addInitializers(decrypter); + } + } + + private List getOrderedBeansOfType(ListableBeanFactory context, + Class type) { List result = new ArrayList(); for (String name : context.getBeanNamesForType(type)) { result.add(context.getBean(name, type)); @@ -191,17 +213,16 @@ public class BootstrapApplicationListener implements public int getOrder() { // Need to run not too late (so not unordered), so that, for instance, the // ContextIdApplicationContextInitializer runs later and picks up the merged - // Environment. Also not too early so that other initializers can pick up the - // parent (especially the Environment). - return Ordered.HIGHEST_PRECEDENCE + 10; + // Environment. Also needs to be quite early so that other initializers can + // pick up the parent (especially the Environment). + return Ordered.HIGHEST_PRECEDENCE + 5; } @Override public void initialize(ConfigurableApplicationContext context) { - preemptMerge( - context.getEnvironment().getPropertySources(), - new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, Collections - . emptyMap())); + preemptMerge(context.getEnvironment().getPropertySources(), + new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, + Collections. emptyMap())); while (context.getParent() != null && context.getParent() != context) { context = (ConfigurableApplicationContext) context.getParent(); } @@ -217,4 +238,26 @@ public class BootstrapApplicationListener implements } } } + + /** + * A special initializer designed to run before the property source bootstrap and + * decrypt any properties needed there (e.g. URL of config server). + */ + @Order(Ordered.HIGHEST_PRECEDENCE + 9) + private static class DelegatingEnvironmentDecryptApplicationInitializer + implements ApplicationContextInitializer { + + private ApplicationContextInitializer delegate; + + public DelegatingEnvironmentDecryptApplicationInitializer( + ApplicationContextInitializer delegate) { + this.delegate = delegate; + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + this.delegate.initialize(applicationContext); + } + + } } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java deleted file mode 100644 index 9087bc98..00000000 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015 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 - * - * http://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.cloud.bootstrap.config; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; -import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; -import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar; -import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * @author Dave Syer - * - */ -@Configuration -public class ConfigurationPropertiesBootstrapConfiguration { - - @Bean - @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) - public ConfigurationPropertiesBeans configurationPropertiesBeans( - ApplicationContext context) { - // Since this is a BeanPostProcessor we have to be super careful not to - // cause a cascade of bean instantiation. Knowing the *name* of the beans we - // need is super optimal, but a little brittle (unfortunately we have no - // choice). - ConfigurationBeanFactoryMetaData metaData = context.getBean( - ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME - + ".store", ConfigurationBeanFactoryMetaData.class); - ConfigurationPropertiesBeans rebinder = new ConfigurationPropertiesBeans(); - rebinder.setBeanMetaDataStore(metaData); - return rebinder; - } - - -} 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 fafa9f1d..f6ae3e8a 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 @@ -78,6 +78,10 @@ public class EnvironmentDecryptApplicationInitializer implements return this.order; } + public void setOrder(int order) { + this.order = order; + } + @Override public void initialize(ConfigurableApplicationContext applicationContext) { diff --git a/spring-cloud-context/src/main/resources/META-INF/spring.factories b/spring-cloud-context/src/main/resources/META-INF/spring.factories index 5be1f276..b18e0c52 100644 --- a/spring-cloud-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-context/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ # AutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\ org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\ org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration @@ -10,7 +11,7 @@ org.springframework.cloud.context.restart.RestartListener # Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -org.springframework.cloud.bootstrap.config.ConfigurationPropertiesBootstrapConfiguration,\ org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\ org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\ +org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\ org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java index 926d4256..b2f81484 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration; import org.springframework.context.annotation.Bean; @@ -104,7 +105,7 @@ public class ConfigurationPropertiesRebinderIntegrationTests { protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration protected static class RebinderConfiguration extends - ConfigurationPropertiesRebinderConfiguration { + ConfigurationPropertiesRebinderAutoConfiguration { } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java index c5252316..01980f3a 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderListIntegrationTests.TestConfiguration; import org.springframework.context.annotation.Bean; @@ -105,7 +106,7 @@ public class ConfigurationPropertiesRebinderListIntegrationTests { // Hack out a protected inner class for testing protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration - protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderConfiguration { + protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderAutoConfiguration { } }