Add extra decryption step before propery source locators

See also the integration tests in spring-cloud-samples/tests

Fixes gh-58
This commit is contained in:
Dave Syer
2015-11-11 09:13:37 +00:00
parent 7f2f008300
commit 988189db82
8 changed files with 151 additions and 128 deletions

View File

@@ -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.<String> emptySet()));
}
}

View File

@@ -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
.<String> emptySet()));
}
}
@ConditionalOnClass(Endpoint.class)
protected static class RefreshEndpointsConfiguration {

View File

@@ -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<ApplicationEnvironmentPreparedEvent>, Ordered {
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, 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.<String, Object> 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<String> names = SpringFactoriesLoader.loadFactoryNames(
BootstrapConfiguration.class, classLoader);
List<String> 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<Class<?>> 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 <T> List<T> getOrderedBeansOfType(ListableBeanFactory context, Class<T> type) {
private void addBootstrapDecryptInitializer(SpringApplication application) {
DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
for (ApplicationContextInitializer<?> initializer : application
.getInitializers()) {
if (initializer instanceof EnvironmentDecryptApplicationInitializer) {
@SuppressWarnings("unchecked")
ApplicationContextInitializer<ConfigurableApplicationContext> delegate = (ApplicationContextInitializer<ConfigurableApplicationContext>) initializer;
decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(
delegate);
}
}
if (decrypter != null) {
application.addInitializers(decrypter);
}
}
private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context,
Class<T> type) {
List<T> result = new ArrayList<T>();
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
.<String, Object> emptyMap()));
preemptMerge(context.getEnvironment().getPropertySources(),
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME,
Collections.<String, Object> 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<ConfigurableApplicationContext> {
private ApplicationContextInitializer<ConfigurableApplicationContext> delegate;
public DelegatingEnvironmentDecryptApplicationInitializer(
ApplicationContextInitializer<ConfigurableApplicationContext> delegate) {
this.delegate = delegate;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
this.delegate.initialize(applicationContext);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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