diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorBindHandler.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorBindHandler.java index 5c14c160..a73dcaaf 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorBindHandler.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorBindHandler.java @@ -33,7 +33,7 @@ import org.springframework.security.crypto.encrypt.TextEncryptor; * @author Marcin Grzejszczak * @since 3.0.0 */ -class TextEncryptorBindHandler extends AbstractBindHandler { +public class TextEncryptorBindHandler extends AbstractBindHandler { private static final Log logger = LogFactory.getLog(TextEncryptorBindHandler.class); @@ -46,7 +46,7 @@ class TextEncryptorBindHandler extends AbstractBindHandler { private final KeyProperties keyProperties; - TextEncryptorBindHandler(TextEncryptor textEncryptor, KeyProperties keyProperties) { + public TextEncryptorBindHandler(TextEncryptor textEncryptor, KeyProperties keyProperties) { this.textEncryptor = textEncryptor; this.keyProperties = keyProperties; } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java index b8205c8a..1ea5e527 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java @@ -20,17 +20,11 @@ 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.EncryptionBootstrapConfiguration; import org.springframework.cloud.bootstrap.encrypt.KeyProperties; import org.springframework.cloud.bootstrap.encrypt.RsaProperties; -import org.springframework.cloud.context.encrypt.EncryptorFactory; -import org.springframework.cloud.util.PropertyUtils; -import org.springframework.core.env.Environment; -import org.springframework.security.crypto.encrypt.TextEncryptor; +import org.springframework.cloud.bootstrap.encrypt.TextEncryptorUtils; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Bootstrapper. @@ -40,7 +34,10 @@ import org.springframework.util.StringUtils; */ public class TextEncryptorConfigBootstrapper implements Bootstrapper { - private static final boolean RSA_IS_PRESENT = ClassUtils + /** + * RsaSecretEncryptor present. + */ + public static final boolean RSA_IS_PRESENT = ClassUtils .isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null); @Override @@ -55,30 +52,11 @@ public class TextEncryptorConfigBootstrapper implements Bootstrapper { 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 EncryptionBootstrapConfiguration.createTextEncryptor(keyProperties, rsaProperties); - } - return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey()); - } - // 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; - }); + TextEncryptorUtils.register(registry); // promote beans to context registry.addCloseListener(event -> { - if (isLegacyBootstrap(event.getApplicationContext().getEnvironment())) { + if (TextEncryptorUtils.isLegacyBootstrap(event.getApplicationContext().getEnvironment())) { return; } BootstrapContext bootstrapContext = event.getBootstrapContext(); @@ -93,59 +71,17 @@ public class TextEncryptorConfigBootstrapper implements Bootstrapper { beanFactory.registerSingleton("rsaProperties", rsaProperties); } } - TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class); - if (textEncryptor != null) { - beanFactory.registerSingleton("textEncryptor", textEncryptor); - } + TextEncryptorUtils.promote(bootstrapContext, beanFactory); }); } + @Deprecated 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; + return TextEncryptorUtils.keysConfigured(properties); } - static boolean hasProperty(Object value) { - if (value instanceof String) { - return StringUtils.hasText((String) value); - } - return value != null; - } - - static boolean isLegacyBootstrap(Environment environment) { - boolean isLegacy = PropertyUtils.useLegacyProcessing(environment); - boolean isBootstrapEnabled = PropertyUtils.bootstrapEnabled(environment); - return isLegacy || isBootstrapEnabled; - } - - /** - * 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 - * - */ - public 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?"); - } + @Deprecated + public static class FailsafeTextEncryptor extends TextEncryptorUtils.FailsafeTextEncryptor { } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java index d3d5c5ba..96ab042e 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/DecryptEnvironmentPostProcessor.java @@ -19,18 +19,13 @@ 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.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; @@ -60,14 +55,15 @@ public class DecryptEnvironmentPostProcessor extends AbstractEnvironmentDecrypt if (bootstrapEnabled(environment) || useLegacyProcessing(environment)) { return; } + if (!ClassUtils.isPresent("org.springframework.security.crypto.encrypt.TextEncryptor", null)) { + return; + } MutablePropertySources propertySources = environment.getPropertySources(); environment.getPropertySources().remove(DECRYPTED_PROPERTY_SOURCE_NAME); - TextEncryptor encryptor = getTextEncryptor(environment); - - Map map = decrypt(encryptor, propertySources); + Map map = TextEncryptorUtils.decrypt(this, environment, propertySources); if (!map.isEmpty()) { // We have some decrypted properties propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map)); @@ -75,21 +71,4 @@ public class DecryptEnvironmentPostProcessor extends AbstractEnvironmentDecrypt } - protected TextEncryptor getTextEncryptor(ConfigurableEnvironment environment) { - Binder binder = Binder.get(environment); - KeyProperties keyProperties = binder.bind(KeyProperties.PREFIX, KeyProperties.class) - .orElseGet(KeyProperties::new); - if (keysConfigured(keyProperties)) { - setFailOnError(keyProperties.isFailOnError()); - if (ClassUtils.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null)) { - RsaProperties rsaProperties = binder.bind(RsaProperties.PREFIX, RsaProperties.class) - .orElseGet(RsaProperties::new); - return EncryptionBootstrapConfiguration.createTextEncryptor(keyProperties, rsaProperties); - } - return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey()); - } - // no keys configured - return new FailsafeTextEncryptor(); - } - } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java index b9a11a79..b43bc0d4 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java @@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 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; @@ -34,7 +33,6 @@ 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; @@ -61,27 +59,16 @@ public class EncryptionBootstrapConfiguration { encryptor = context.getBean(TextEncryptor.class); } catch (NoSuchBeanDefinitionException e) { - encryptor = new FailsafeTextEncryptor(); + encryptor = new TextEncryptorUtils.FailsafeTextEncryptor(); } EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(encryptor); listener.setFailOnError(keyProperties.isFailOnError()); return listener; } + @Deprecated public static TextEncryptor createTextEncryptor(KeyProperties keyProperties, RsaProperties rsaProperties) { - 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()); + return TextEncryptorUtils.createTextEncryptor(keyProperties, rsaProperties); } @Configuration(proxyBeanMethods = false) @@ -99,7 +86,7 @@ public class EncryptionBootstrapConfiguration { @Bean @ConditionalOnMissingBean(TextEncryptor.class) public TextEncryptor textEncryptor(KeyProperties keyProperties, RsaProperties rsaProperties) { - return createTextEncryptor(keyProperties, rsaProperties); + return TextEncryptorUtils.createTextEncryptor(keyProperties, rsaProperties); } } @@ -150,26 +137,8 @@ public class EncryptionBootstrapConfiguration { } - /** - * 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?"); - } + @Deprecated + protected static class FailsafeTextEncryptor extends TextEncryptorUtils.FailsafeTextEncryptor { } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java new file mode 100644 index 00000000..2c1b7354 --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/TextEncryptorUtils.java @@ -0,0 +1,193 @@ +/* + * Copyright 2013-2021 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.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.cloud.bootstrap.TextEncryptorBindHandler; +import org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper; +import org.springframework.cloud.context.encrypt.EncryptorFactory; +import org.springframework.cloud.util.PropertyUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +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; + +public abstract class TextEncryptorUtils { + + /** + * Decrypt environment. See {@link DecryptEnvironmentPostProcessor}. + * @param decryptor the {@link AbstractEnvironmentDecrypt} + * @param environment the environment to get key properties from. + * @param propertySources the property sources to decrypt. + * @return the decrypted properties. + */ + static Map decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment, + MutablePropertySources propertySources) { + TextEncryptor encryptor = getTextEncryptor(decryptor, environment); + return decryptor.decrypt(encryptor, propertySources); + } + + static TextEncryptor getTextEncryptor(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) { + Binder binder = Binder.get(environment); + KeyProperties keyProperties = binder.bind(KeyProperties.PREFIX, KeyProperties.class) + .orElseGet(KeyProperties::new); + if (TextEncryptorUtils.keysConfigured(keyProperties)) { + decryptor.setFailOnError(keyProperties.isFailOnError()); + if (ClassUtils.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null)) { + RsaProperties rsaProperties = binder.bind(RsaProperties.PREFIX, RsaProperties.class) + .orElseGet(RsaProperties::new); + return TextEncryptorUtils.createTextEncryptor(keyProperties, rsaProperties); + } + return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey()); + } + // no keys configured + return new TextEncryptorUtils.FailsafeTextEncryptor(); + } + + /** + * Register all classes that need a {@link TextEncryptor} in {@link TextEncryptorConfigBootstrapper}. + * @param registry the BootstrapRegistry. + */ + public static void register(BootstrapRegistry registry) { + registry.registerIfAbsent(TextEncryptor.class, context -> { + KeyProperties keyProperties = context.get(KeyProperties.class); + if (TextEncryptorConfigBootstrapper.keysConfigured(keyProperties)) { + if (TextEncryptorConfigBootstrapper.RSA_IS_PRESENT) { + RsaProperties rsaProperties = context.get(RsaProperties.class); + return createTextEncryptor(keyProperties, rsaProperties); + } + return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey()); + } + // 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 the {@link TextEncryptor} to the {@link ApplicationContext}. + * @param bootstrapContext the Context. + * @param beanFactory the bean factory. + */ + public static void promote(BootstrapContext bootstrapContext, ConfigurableListableBeanFactory beanFactory) { + TextEncryptor textEncryptor = bootstrapContext.get(TextEncryptor.class); + if (textEncryptor != null) { + beanFactory.registerSingleton("textEncryptor", textEncryptor); + } + } + + /** + * Utility to create a {@link TextEncryptor} via properties. + * @param keyProperties the Key properties. + * @param rsaProperties RSA properties. + * @return created {@link TextEncryptor}. + */ + public static TextEncryptor createTextEncryptor(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()); + } + + /** + * Is a key configured. + * @param properties the Key properties. + * @return true if configured. + */ + 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; + } + + /** + * Method to check if legacy bootstrap mode is enabled. This is either if the + * boot legacy processing property is set or spring.cloud.bootstrap.enabled=true. + * @param environment where to check properties. + * @return true if bootstrap enabled. + */ + public static boolean isLegacyBootstrap(Environment environment) { + boolean isLegacy = PropertyUtils.useLegacyProcessing(environment); + boolean isBootstrapEnabled = PropertyUtils.bootstrapEnabled(environment); + return isLegacy || isBootstrapEnabled; + } + + /** + * 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 + * + */ + public 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?"); + } + + } + +}