Creates DecryptEnvironmentPostProcessor.

This is used if bootstrap and legacy processing are not enabled.

EnvironmentDecryptApplicationInitializer is only is if bootstrap and legacy processing are enabled.

Fixes gh-815
This commit is contained in:
spencergibb
2020-12-18 16:02:57 -05:00
parent 7211cdd2e8
commit f77a6ec372
13 changed files with 510 additions and 523 deletions

View File

@@ -1,170 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
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.SpringBootCondition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.TextEncryptorConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties.KeyStore;
import org.springframework.cloud.bootstrap.encrypt.RsaProperties;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ TextEncryptor.class })
@ConditionalOnProperty(value = "spring.config.use-legacy-processing", havingValue = "false")
public class EncryptionBootstrapAutoConfiguration {
@Autowired(required = false)
private TextEncryptor encryptor;
@Autowired
private KeyProperties key;
@Bean
@ConfigurationPropertiesBinding
TextEncryptorConfigurationPropertiesBindHandlerAdvisor textEncryptorConfigurationPropertiesBindHandlerAdvisor(ApplicationContext applicationContext) {
return new TextEncryptorConfigurationPropertiesBindHandlerAdvisor(applicationContext);
}
@Configuration(proxyBeanMethods = false)
@Conditional(KeyCondition.class)
@ConditionalOnClass(RsaSecretEncryptor.class)
@EnableConfigurationProperties({ RsaProperties.class })
protected static class RsaEncryptionConfiguration {
@Autowired
private KeyProperties key;
@Autowired
private RsaProperties rsaProperties;
@Bean
@ConditionalOnMissingBean(TextEncryptor.class)
@ConfigurationPropertiesBinding
public TextEncryptor textEncryptor() {
KeyStore keyStore = this.key.getKeyStore();
if (keyStore.getLocation() != null) {
if (keyStore.getLocation().exists()) {
return new RsaSecretEncryptor(
new KeyStoreKeyFactory(keyStore.getLocation(), keyStore.getPassword().toCharArray())
.getKeyPair(keyStore.getAlias(), keyStore.getSecret().toCharArray()),
this.rsaProperties.getAlgorithm(), this.rsaProperties.getSalt(),
this.rsaProperties.isStrong());
}
throw new IllegalStateException("Invalid keystore location");
}
return new EncryptorFactory(this.key.getSalt()).create(this.key.getKey());
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(KeyCondition.class)
@ConditionalOnMissingClass("org.springframework.security.rsa.crypto.RsaSecretEncryptor")
protected static class VanillaEncryptionConfiguration {
@Autowired
private KeyProperties key;
@Bean
@ConditionalOnMissingBean(TextEncryptor.class)
@ConfigurationPropertiesBinding
public TextEncryptor textEncryptor() {
return new EncryptorFactory(this.key.getSalt()).create(this.key.getKey());
}
}
/**
* A Spring Boot condition for key encryption.
*/
public static class KeyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
if (hasProperty(environment, "encrypt.key-store.location")) {
if (hasProperty(environment, "encrypt.key-store.password")) {
return ConditionOutcome.match("Keystore found in Environment");
}
return ConditionOutcome.noMatch("Keystore found but no password in Environment");
}
else if (hasProperty(environment, "encrypt.key")) {
return ConditionOutcome.match("Key found in Environment");
}
return ConditionOutcome.noMatch("Keystore nor key found in Environment");
}
private boolean hasProperty(Environment environment, String key) {
String value = environment.getProperty(key);
if (value == null) {
return false;
}
return StringUtils.hasText(environment.resolvePlaceholders(value));
}
}
/**
* TextEncryptor that just fails, so that users don't get a false sense of security
* adding ciphers to config files and not getting them decrypted.
*
* @author Dave Syer
*
*/
protected static class FailsafeTextEncryptor implements TextEncryptor {
@Override
public String encrypt(String text) {
throw new UnsupportedOperationException(
"No encryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
}
@Override
public String decrypt(String encryptedText) {
throw new UnsupportedOperationException(
"No decryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2013-2020 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
*
* https://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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* BindHandler that uses a TextEncryptor to decrypt text if properly prefixed with
* {cipher}.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class TextEncryptorBindHandler extends AbstractBindHandler {
private static final Log logger = LogFactory.getLog(TextEncryptorBindHandler.class);
/**
* Prefix indicating an encrypted value.
*/
protected static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
private final TextEncryptor textEncryptor;
private final KeyProperties keyProperties;
TextEncryptorBindHandler(TextEncryptor textEncryptor, KeyProperties keyProperties) {
this.textEncryptor = textEncryptor;
this.keyProperties = keyProperties;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
if (result instanceof String && ((String) result).startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return decrypt(name.toString(), (String) result);
}
return result;
}
private String decrypt(String key, String original) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = this.textEncryptor.decrypt(value);
if (logger.isDebugEnabled()) {
logger.debug("Decrypted: key=" + key);
}
return value;
}
catch (Exception e) {
String message = "Cannot decrypt: key=" + key;
if (logger.isDebugEnabled()) {
logger.warn(message, e);
}
else {
logger.warn(message);
}
if (this.keyProperties.isFailOnError()) {
throw new IllegalStateException(message, e);
}
return "";
}
}
}

View File

@@ -16,70 +16,130 @@
package org.springframework.cloud.bootstrap;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.Bootstrapper;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.cloud.bootstrap.encrypt.RsaProperties;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Bootstrapper
* Bootstrapper.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
public class TextEncryptorConfigBootstrapper implements Bootstrapper {
private static final boolean RSA_IS_PRESENT = ClassUtils
.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null);
@Override
public void intitialize(BootstrapRegistry registry) {
if (!ClassUtils.isPresent("org.springframework.security.crypto.encrypt.TextEncryptor", null)) {
return;
}
registry.registerIfAbsent(KeyProperties.class,
context -> context.get(Binder.class)
.bind("encrypt", KeyProperties.class)
.orElseGet(KeyProperties::new));
registry.registerIfAbsent(RsaProperties.class,
context -> context.get(Binder.class)
.bind("encrypt.rsa", RsaProperties.class)
.orElseGet(RsaProperties::new));
/*registry.registerIfAbsent(TextEncryptorConfigurationPropertiesBindHandlerAdvisor.class, context -> {
if (isLegacyProcessingEnabled(context.get(Binder.class))) {
return null;
registry.registerIfAbsent(KeyProperties.class, context -> context.get(Binder.class)
.bind(KeyProperties.PREFIX, KeyProperties.class).orElseGet(KeyProperties::new));
if (RSA_IS_PRESENT) {
registry.registerIfAbsent(RsaProperties.class, context -> context.get(Binder.class)
.bind(RsaProperties.PREFIX, RsaProperties.class).orElseGet(RsaProperties::new));
}
registry.registerIfAbsent(TextEncryptor.class, context -> {
KeyProperties keyProperties = context.get(KeyProperties.class);
if (keysConfigured(keyProperties)) {
if (RSA_IS_PRESENT) {
RsaProperties rsaProperties = context.get(RsaProperties.class);
return rsaTextEncryptor(keyProperties, rsaProperties);
}
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
return new TextEncryptorConfigurationPropertiesBindHandlerAdvisor(context.get(TextEncryptor.class));
});*/
// no keys configured
return new FailsafeTextEncryptor();
});
registry.registerIfAbsent(BindHandler.class, context -> {
TextEncryptor textEncryptor = context.get(TextEncryptor.class);
if (textEncryptor != null) {
KeyProperties keyProperties = context.get(KeyProperties.class);
return new TextEncryptorBindHandler(textEncryptor, keyProperties);
}
return null;
});
// promote beans to context
registry.addCloseListener(event -> {
if (isLegacyProcessingEnabled(event.getApplicationContext().getEnvironment())) {
if (isLegacyBootstrap(event.getApplicationContext().getEnvironment())) {
return;
}
KeyProperties keyProperties = event.getBootstrapContext().get(KeyProperties.class);
BootstrapContext bootstrapContext = event.getBootstrapContext();
KeyProperties keyProperties = bootstrapContext.get(KeyProperties.class);
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
if (keyProperties != null) {
event.getApplicationContext().getBeanFactory().registerSingleton("keyProperties",
keyProperties);
beanFactory.registerSingleton("keyProperties", keyProperties);
}
RsaProperties rsaProperties = event.getBootstrapContext().get(RsaProperties.class);
if (rsaProperties != null) {
event.getApplicationContext().getBeanFactory().registerSingleton("rsaProperties",
rsaProperties);
if (RSA_IS_PRESENT) {
RsaProperties rsaProperties = bootstrapContext.get(RsaProperties.class);
if (rsaProperties != null) {
beanFactory.registerSingleton("rsaProperties", rsaProperties);
}
}
TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class);
if (textEncryptor != null) {
beanFactory.registerSingleton("textEncryptor", textEncryptor);
}
});
}
private boolean isLegacyProcessingEnabled(Binder binder) {
return binder.bind("spring.config.use-legacy-processing", Boolean.class).orElse(false);
public static TextEncryptor rsaTextEncryptor(KeyProperties keyProperties, RsaProperties rsaProperties) {
KeyProperties.KeyStore keyStore = keyProperties.getKeyStore();
if (keyStore.getLocation() != null) {
if (keyStore.getLocation().exists()) {
return new RsaSecretEncryptor(
new KeyStoreKeyFactory(keyStore.getLocation(), keyStore.getPassword().toCharArray())
.getKeyPair(keyStore.getAlias(), keyStore.getSecret().toCharArray()),
rsaProperties.getAlgorithm(), rsaProperties.getSalt(), rsaProperties.isStrong());
}
throw new IllegalStateException("Invalid keystore location");
}
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
private boolean isLegacyProcessingEnabled(Environment environment) {
return environment.getProperty("spring.config.use-legacy-processing", Boolean.class, false);
public static boolean keysConfigured(KeyProperties properties) {
if (hasProperty(properties.getKeyStore().getLocation())) {
if (hasProperty(properties.getKeyStore().getPassword())) {
return true;
}
return false;
}
else if (hasProperty(properties.getKey())) {
return true;
}
return false;
}
static boolean hasProperty(Object value) {
if (value instanceof String) {
return StringUtils.hasText((String) value);
}
return value != null;
}
static boolean isLegacyBootstrap(Environment environment) {
boolean isLegacy = environment.getProperty("spring.config.use-legacy-processing", Boolean.class, false);
boolean isBootstrapEnabled = environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, false);
return isLegacy || isBootstrapEnabled;
}
/**
@@ -89,7 +149,7 @@ public class TextEncryptorConfigBootstrapper implements Bootstrapper {
* @author Dave Syer
*
*/
protected static class FailsafeTextEncryptor implements TextEncryptor {
public static class FailsafeTextEncryptor implements TextEncryptor {
@Override
public String encrypt(String text) {
@@ -104,4 +164,5 @@ public class TextEncryptorConfigBootstrapper implements Bootstrapper {
}
}
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.encrypt.TextEncryptor;
public class TextEncryptorConfigurationPropertiesBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
private final ApplicationContext applicationContext;
public TextEncryptorConfigurationPropertiesBindHandlerAdvisor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public BindHandler apply(BindHandler bindHandler) {
return wrappedBindHandler(bindHandler);
}
private BindHandler wrappedBindHandler(BindHandler handler) {
KeyProperties keyProperties = this.applicationContext.getBean(KeyProperties.class);
TextEncryptorBindHandler textEncryptorBindHandler = new TextEncryptorBindHandler(this.applicationContext.getBeanProvider(TextEncryptor.class).getIfAvailable(FailsafeTextEncryptor::new), keyProperties);
return new BindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
return handler.onStart(name, target, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
Object defaultHandlerResult = handler.onSuccess(name, target, context, result);
return textEncryptorBindHandler.onSuccess(name, target, context, defaultHandlerResult);
}
@Override
public Object onCreate(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
return handler.onCreate(name, target, context, result);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
return handler.onFailure(name, target, context, error);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) throws Exception {
handler.onFinish(name, target, context, result);
}
};
}
/**
* TextEncryptor that just fails, so that users don't get a false sense of security
* adding ciphers to config files and not getting them decrypted.
*
* @author Dave Syer
*
*/
protected static class FailsafeTextEncryptor implements TextEncryptor {
@Override
public String encrypt(String text) {
throw new UnsupportedOperationException(
"No encryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
}
@Override
public String decrypt(String encryptedText) {
throw new UnsupportedOperationException(
"No decryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
}
}
protected static class TextEncryptorBindHandler extends AbstractBindHandler {
private static final Log logger = LogFactory.getLog(TextEncryptorBindHandler.class);
/**
* Prefix indicating an encrypted value.
*/
protected static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
private final TextEncryptor textEncryptor;
private final KeyProperties keyProperties;
public TextEncryptorBindHandler(TextEncryptor textEncryptor, KeyProperties keyProperties) {
this.textEncryptor = textEncryptor;
this.keyProperties = keyProperties;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
if (result instanceof String) {
return decrypt(name.toString(), (String) result);
}
return result;
}
private String decrypt(String key, String original) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = this.textEncryptor.decrypt(value);
if (logger.isDebugEnabled()) {
logger.debug("Decrypted: key=" + key);
}
return value;
}
catch (Exception e) {
String message = "Cannot decrypt: key=" + key;
if (logger.isDebugEnabled()) {
logger.warn(message, e);
}
else {
logger.warn(message);
}
if (this.keyProperties.isFailOnError()) {
throw new IllegalStateException(message, e);
}
return "";
}
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2013-2020 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
*
* https://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.encrypt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* Abstract class that handles decrypting and merging of PropertySources.
*/
public class AbstractEnvironmentDecrypt {
private static final Pattern COLLECTION_PROPERTY = Pattern.compile("(\\S+)?\\[(\\d+)\\](\\.\\S+)?");
/**
* Name of the decrypted property source.
*/
public static final String DECRYPTED_PROPERTY_SOURCE_NAME = "decrypted";
/**
* Prefix indicating an encrypted value.
*/
public static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
protected Log logger = LogFactory.getLog(getClass());
private boolean failOnError = true;
/**
* Strategy to determine how to handle exceptions during decryption.
* @param failOnError the flag value (default true)
*/
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public boolean isFailOnError() {
return this.failOnError;
}
protected Map<String, Object> decrypt(TextEncryptor encryptor, PropertySources propertySources) {
Map<String, Object> properties = merge(propertySources);
decrypt(encryptor, properties);
return properties;
}
protected Map<String, Object> merge(PropertySources propertySources) {
Map<String, Object> properties = new LinkedHashMap<>();
List<PropertySource<?>> sources = new ArrayList<>();
for (PropertySource<?> source : propertySources) {
sources.add(0, source);
}
for (PropertySource<?> source : sources) {
merge(source, properties);
}
return properties;
}
protected void merge(PropertySource<?> source, Map<String, Object> properties) {
if (source instanceof CompositePropertySource) {
List<PropertySource<?>> sources = new ArrayList<>(((CompositePropertySource) source).getPropertySources());
Collections.reverse(sources);
for (PropertySource<?> nested : sources) {
merge(nested, properties);
}
}
else if (source instanceof EnumerablePropertySource) {
Map<String, Object> otherCollectionProperties = new LinkedHashMap<>();
boolean sourceHasDecryptedCollection = false;
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
for (String key : enumerable.getPropertyNames()) {
Object property = source.getProperty(key);
if (property != null) {
String value = property.toString();
if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
properties.put(key, value);
if (COLLECTION_PROPERTY.matcher(key).matches()) {
sourceHasDecryptedCollection = true;
}
}
else if (COLLECTION_PROPERTY.matcher(key).matches()) {
// put non-encrypted properties so merging of index properties
// happens correctly
otherCollectionProperties.put(key, value);
}
else {
// override previously encrypted with non-encrypted property
properties.remove(key);
}
}
}
// copy all indexed properties even if not encrypted
if (sourceHasDecryptedCollection && !otherCollectionProperties.isEmpty()) {
properties.putAll(otherCollectionProperties);
}
}
}
protected void decrypt(TextEncryptor encryptor, Map<String, Object> properties) {
properties.replaceAll((key, value) -> {
String valueString = value.toString();
if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return value;
}
return decrypt(encryptor, key, valueString);
});
}
protected String decrypt(TextEncryptor encryptor, String key, String original) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = encryptor.decrypt(value);
if (logger.isDebugEnabled()) {
logger.debug("Decrypted: key=" + key);
}
return value;
}
catch (Exception e) {
String message = "Cannot decrypt: key=" + key;
if (logger.isDebugEnabled()) {
logger.warn(message, e);
}
else {
logger.warn(message);
}
if (this.failOnError) {
throw new IllegalStateException(message, e);
}
return "";
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2013-2020 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
*
* https://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.encrypt;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper;
import org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper.FailsafeTextEncryptor;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.ClassUtils;
import static org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper.keysConfigured;
import static org.springframework.cloud.util.PropertyUtils.bootstrapEnabled;
import static org.springframework.cloud.util.PropertyUtils.useLegacyProcessing;
/**
* Decrypt properties from the environment and insert them with high priority so they
* override the encrypted values.
*
* @author Dave Syer
* @author Tim Ysewyn
*/
public class DecryptEnvironmentPostProcessor extends AbstractEnvironmentDecrypt
implements EnvironmentPostProcessor, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (bootstrapEnabled(environment) || useLegacyProcessing(environment)) {
return;
}
MutablePropertySources propertySources = environment.getPropertySources();
environment.getPropertySources().remove(DECRYPTED_PROPERTY_SOURCE_NAME);
TextEncryptor encryptor = getTextEncryptor(environment);
Map<String, Object> map = decrypt(encryptor, propertySources);
if (!map.isEmpty()) {
// We have some decrypted properties
propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map));
}
}
protected TextEncryptor getTextEncryptor(ConfigurableEnvironment environment) {
Binder binder = Binder.get(environment);
KeyProperties keyProperties = binder.bind(KeyProperties.PREFIX, KeyProperties.class)
.orElseGet(KeyProperties::new);
if (keysConfigured(keyProperties)) {
if (ClassUtils.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null)) {
RsaProperties rsaProperties = binder.bind(RsaProperties.PREFIX, RsaProperties.class)
.orElseGet(RsaProperties::new);
return TextEncryptorConfigBootstrapper.rsaTextEncryptor(keyProperties, rsaProperties);
}
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
// no keys configured
return new FailsafeTextEncryptor();
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.bootstrap.encrypt;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -25,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties.KeyStore;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
@@ -42,55 +44,58 @@ import org.springframework.util.StringUtils;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ TextEncryptor.class })
@EnableConfigurationProperties({ KeyProperties.class })
@EnableConfigurationProperties
public class EncryptionBootstrapConfiguration {
@Autowired(required = false)
private TextEncryptor encryptor;
@Autowired
private KeyProperties key;
@Bean
@ConditionalOnMissingBean
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Bean
public EnvironmentDecryptApplicationInitializer environmentDecryptApplicationListener() {
if (this.encryptor == null) {
this.encryptor = new FailsafeTextEncryptor();
public EnvironmentDecryptApplicationInitializer environmentDecryptApplicationListener(
ConfigurableApplicationContext context, KeyProperties keyProperties) {
TextEncryptor encryptor;
try {
encryptor = context.getBean(TextEncryptor.class);
}
EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(
this.encryptor);
listener.setFailOnError(this.key.isFailOnError());
catch (NoSuchBeanDefinitionException e) {
encryptor = new FailsafeTextEncryptor();
}
EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(encryptor);
listener.setFailOnError(keyProperties.isFailOnError());
return listener;
}
@Configuration(proxyBeanMethods = false)
@Conditional(KeyCondition.class)
@ConditionalOnClass(RsaSecretEncryptor.class)
@EnableConfigurationProperties({ RsaProperties.class })
@EnableConfigurationProperties
protected static class RsaEncryptionConfiguration {
@Autowired
private KeyProperties key;
@Autowired
private RsaProperties rsaProperties;
@Bean
@ConditionalOnMissingBean
public RsaProperties rsaProperties() {
return new RsaProperties();
}
@Bean
@ConditionalOnMissingBean(TextEncryptor.class)
public TextEncryptor textEncryptor() {
KeyStore keyStore = this.key.getKeyStore();
public TextEncryptor textEncryptor(RsaProperties rsaProperties, KeyProperties keyProperties) {
KeyStore keyStore = keyProperties.getKeyStore();
if (keyStore.getLocation() != null) {
if (keyStore.getLocation().exists()) {
return new RsaSecretEncryptor(
new KeyStoreKeyFactory(keyStore.getLocation(), keyStore.getPassword().toCharArray())
.getKeyPair(keyStore.getAlias(), keyStore.getSecret().toCharArray()),
this.rsaProperties.getAlgorithm(), this.rsaProperties.getSalt(),
this.rsaProperties.isStrong());
rsaProperties.getAlgorithm(), rsaProperties.getSalt(), rsaProperties.isStrong());
}
throw new IllegalStateException("Invalid keystore location");
}
return new EncryptorFactory(this.key.getSalt()).create(this.key.getKey());
return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
}
}

View File

@@ -16,17 +16,10 @@
package org.springframework.cloud.bootstrap.encrypt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.bootstrap.BootstrapApplicationListener;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
@@ -34,15 +27,15 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import static org.springframework.cloud.util.PropertyUtils.bootstrapEnabled;
import static org.springframework.cloud.util.PropertyUtils.useLegacyProcessing;
/**
* Decrypt properties from the environment and insert them with high priority so they
* override the encrypted values.
@@ -50,42 +43,18 @@ import org.springframework.security.crypto.encrypt.TextEncryptor;
* @author Dave Syer
* @author Tim Ysewyn
*/
public class EnvironmentDecryptApplicationInitializer
public class EnvironmentDecryptApplicationInitializer extends AbstractEnvironmentDecrypt
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
/**
* Name of the decrypted property source.
*/
public static final String DECRYPTED_PROPERTY_SOURCE_NAME = "decrypted";
/**
* Name of the decrypted bootstrap property source.
*/
public static final String DECRYPTED_BOOTSTRAP_PROPERTY_SOURCE_NAME = "decryptedBootstrap";
/**
* Prefix indicating an encrypted value.
*/
public static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
private static final Pattern COLLECTION_PROPERTY = Pattern.compile("(\\S+)?\\[(\\d+)\\](\\.\\S+)?");
private static Log logger = LogFactory.getLog(EnvironmentDecryptApplicationInitializer.class);
private int order = Ordered.HIGHEST_PRECEDENCE + 15;
private TextEncryptor encryptor;
private boolean failOnError = true;
/**
* Strategy to determine how to handle exceptions during decryption.
* @param failOnError the flag value (default true)
*/
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public EnvironmentDecryptApplicationInitializer(TextEncryptor encryptor) {
this.encryptor = encryptor;
}
@@ -102,6 +71,10 @@ public class EnvironmentDecryptApplicationInitializer
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
return;
}
MutablePropertySources propertySources = environment.getPropertySources();
Set<String> found = new LinkedHashSet<>();
@@ -119,7 +92,7 @@ public class EnvironmentDecryptApplicationInitializer
}
}
removeDecryptedProperties(applicationContext);
Map<String, Object> map = decrypt(propertySources);
Map<String, Object> map = decrypt(this.encryptor, propertySources);
if (!map.isEmpty()) {
// We have some decrypted properties
found.addAll(map.keySet());
@@ -173,27 +146,9 @@ public class EnvironmentDecryptApplicationInitializer
}
}
public Map<String, Object> decrypt(PropertySources propertySources) {
Map<String, Object> properties = merge(propertySources);
decrypt(properties);
return properties;
}
private Map<String, Object> decrypt(PropertySource<?> source) {
Map<String, Object> properties = merge(source);
decrypt(properties);
return properties;
}
private Map<String, Object> merge(PropertySources propertySources) {
Map<String, Object> properties = new LinkedHashMap<>();
List<PropertySource<?>> sources = new ArrayList<>();
for (PropertySource<?> source : propertySources) {
sources.add(0, source);
}
for (PropertySource<?> source : sources) {
merge(source, properties);
}
decrypt(this.encryptor, properties);
return properties;
}
@@ -203,83 +158,4 @@ public class EnvironmentDecryptApplicationInitializer
return properties;
}
private void merge(PropertySource<?> source, Map<String, Object> properties) {
if (source instanceof CompositePropertySource) {
List<PropertySource<?>> sources = new ArrayList<>(((CompositePropertySource) source).getPropertySources());
Collections.reverse(sources);
for (PropertySource<?> nested : sources) {
merge(nested, properties);
}
}
else if (source instanceof EnumerablePropertySource) {
Map<String, Object> otherCollectionProperties = new LinkedHashMap<>();
boolean sourceHasDecryptedCollection = false;
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
for (String key : enumerable.getPropertyNames()) {
Object property = source.getProperty(key);
if (property != null) {
String value = property.toString();
if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
properties.put(key, value);
if (COLLECTION_PROPERTY.matcher(key).matches()) {
sourceHasDecryptedCollection = true;
}
}
else if (COLLECTION_PROPERTY.matcher(key).matches()) {
// put non-encrypted properties so merging of index properties
// happens correctly
otherCollectionProperties.put(key, value);
}
else {
// override previously encrypted with non-encrypted property
properties.remove(key);
}
}
}
// copy all indexed properties even if not encrypted
if (sourceHasDecryptedCollection && !otherCollectionProperties.isEmpty()) {
properties.putAll(otherCollectionProperties);
}
}
}
private void decrypt(Map<String, Object> properties) {
properties.replaceAll((key, value) -> {
String valueString = value.toString();
if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return value;
}
return decrypt(key, valueString);
});
}
private String decrypt(String key, String original) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = this.encryptor.decrypt(value);
if (logger.isDebugEnabled()) {
logger.debug("Decrypted: key=" + key);
}
return value;
}
catch (Exception e) {
String message = "Cannot decrypt: key=" + key;
if (logger.isDebugEnabled()) {
logger.warn(message, e);
}
else {
logger.warn(message);
}
if (this.failOnError) {
throw new IllegalStateException(message, e);
}
return "";
}
}
}

View File

@@ -24,9 +24,14 @@ import org.springframework.core.io.Resource;
*
* @author Dave Syer
*/
@ConfigurationProperties("encrypt")
@ConfigurationProperties(KeyProperties.PREFIX)
public class KeyProperties {
/**
* ConfigurationProperties prefix for KeyProperties.
*/
public static final String PREFIX = "encrypt";
/**
* A symmetric key. As a stronger alternative, consider using a keystore.
*/

View File

@@ -24,9 +24,14 @@ import org.springframework.security.rsa.crypto.RsaAlgorithm;
* @author Ryan Baxter
*/
@ConditionalOnClass(RsaAlgorithm.class)
@ConfigurationProperties("encrypt.rsa")
@ConfigurationProperties(RsaProperties.PREFIX)
public class RsaProperties {
/**
* ConfigurationProperties prefix for RsaProperties.
*/
public static final String PREFIX = "encrypt.rsa";
/**
* The RSA algorithm to use (DEFAULT or OEAP). Once it is set, do not change it (or
* existing ciphers will not be decryptable).

View File

@@ -4,19 +4,22 @@ org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfi
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.EncryptionBootstrapAutoConfiguration
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
# Bootstrap components
# Spring Cloud Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
# Spring Boot Bootstrappers
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper
org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bootstrap.encrypt.DecryptEnvironmentPostProcessor

View File

@@ -87,7 +87,7 @@ public class EncryptionBootstrapConfigurationTests {
then(false).as("Should not create an application context with invalid keystore location").isTrue();
}
catch (Exception e) {
then(e).hasRootCauseInstanceOf(IllegalStateException.class);
then(e).isInstanceOf(IllegalStateException.class);
}
}

View File

@@ -64,7 +64,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
@Test
public void decryptCipherKey() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("foo: {cipher}bar").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context);
this.listener.initialize(context);
then(context.getEnvironment().getProperty("foo")).isEqualTo("bar");
}
@@ -72,8 +72,8 @@ public class EnvironmentDecryptApplicationInitializerTests {
@Test
public void relaxedBinding() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("FOO_TEXT: {cipher}bar").applyTo(context.getEnvironment(),
TestPropertyValues.Type.SYSTEM_ENVIRONMENT);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "FOO_TEXT: {cipher}bar")
.applyTo(context.getEnvironment(), TestPropertyValues.Type.SYSTEM_ENVIRONMENT);
this.listener.initialize(context);
then(context.getEnvironment().getProperty("foo.text")).isEqualTo("bar");
}
@@ -81,7 +81,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
@Test
public void propertySourcesOrderedCorrectly() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("foo: {cipher}bar").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context);
context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "{cipher}spam")));
this.listener.initialize(context);
@@ -92,7 +92,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
public void errorOnDecrypt() {
this.listener = new EnvironmentDecryptApplicationInitializer(Encryptors.text("deadbeef", "AFFE37"));
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("foo: {cipher}bar").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context);
// catch IllegalStateException and verify
try {
this.listener.initialize(context);
@@ -110,7 +110,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
this.listener = new EnvironmentDecryptApplicationInitializer(Encryptors.text("deadbeef", "AFFE37"));
this.listener.setFailOnError(false);
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("foo: {cipher}bar").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context);
this.listener.initialize(context);
// Assert logs contain warning
String sysOutput = this.outputCapture.toString();
@@ -125,7 +125,9 @@ public class EnvironmentDecryptApplicationInitializerTests {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
// tests that collections in another property source don't get copied into
// "decrypted" property source
TestPropertyValues.of("yours[0].someValue: yourFoo", "yours[1].someValue: yourBar").applyTo(context);
TestPropertyValues
.of("spring.cloud.bootstrap.enabled=true", "yours[0].someValue: yourFoo", "yours[1].someValue: yourBar")
.applyTo(context);
// collection with some encrypted keys and some not encrypted
TestPropertyValues
@@ -153,7 +155,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
EnvironmentDecryptApplicationInitializer initializer = new EnvironmentDecryptApplicationInitializer(
Encryptors.noOpText());
TestPropertyValues.of("key:{cipher}value").applyTo(ctx);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "key:{cipher}value").applyTo(ctx);
ApplicationContext ctxParent = mock(ApplicationContext.class);
when(ctxParent.getEnvironment()).thenReturn(mock(Environment.class));
@@ -168,6 +170,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
@Test
public void testDecryptCompositePropertySource() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true").applyTo(ctx);
EnvironmentDecryptApplicationInitializer initializer = new EnvironmentDecryptApplicationInitializer(
Encryptors.noOpText());
@@ -190,7 +193,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
@Test
public void propertySourcesOrderedCorrectlyWithUnencryptedOverrides() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("foo: {cipher}bar").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar").applyTo(context);
context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "spam")));
this.listener.initialize(context);
@@ -208,6 +211,7 @@ public class EnvironmentDecryptApplicationInitializerTests {
EnvironmentDecryptApplicationInitializer initializer = new EnvironmentDecryptApplicationInitializer(encryptor);
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true").applyTo(context);
CompositePropertySource bootstrap = new CompositePropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME);
bootstrap.addPropertySource(
new MapPropertySource("configService", Collections.singletonMap("foo", "{cipher}bar")));
@@ -248,7 +252,8 @@ public class EnvironmentDecryptApplicationInitializerTests {
TextEncryptor encryptor = mock(TextEncryptor.class);
when(encryptor.decrypt("bar2")).thenReturn("bar2");
EnvironmentDecryptApplicationInitializer initializer = new EnvironmentDecryptApplicationInitializer(encryptor);
TestPropertyValues.of("foo: {cipher}bar", "foo2: {cipher}bar2").applyTo(context);
TestPropertyValues.of("spring.cloud.bootstrap.enabled=true", "foo: {cipher}bar", "foo2: {cipher}bar2")
.applyTo(context);
context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("test_override", Collections.singletonMap("foo", "spam")));
initializer.initialize(context);