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:
@@ -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?");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user