Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,19 @@

package org.springframework.cloud.bootstrap.encrypt;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
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.core.env.CompositePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
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;

/**
Expand Down Expand Up @@ -66,78 +65,73 @@ public boolean isFailOnError() {
}

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);
}
Map<String, Object> decryptedProperties = new LinkedHashMap<>();
var visitor = new PropertyVisitor();

for (PropertySource<?> propertySource : propertySources) {
if (propertySource instanceof EnumerablePropertySource<?> enumerable) {
for (String propertyName : enumerable.getPropertyNames()) {
if (visitor.isVisited(propertyName)) {
continue;
}

}
else if (source instanceof EnumerablePropertySource<?> enumerable) {
Map<String, Object> otherCollectionProperties = new LinkedHashMap<>();
boolean sourceHasDecryptedCollection = false;

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;
var collectionMatcher = COLLECTION_PROPERTY.matcher(propertyName);
if (collectionMatcher.matches()) {
// It is an indexed property. All items should be checked.
var prefix = collectionMatcher.group(1);
if (prefix == null) {
prefix = "";
}
}
else if (COLLECTION_PROPERTY.matcher(key).matches()) {
// put non-encrypted properties so merging of index properties
// happens correctly
otherCollectionProperties.put(key, value);
var indexed = getPropertyValues(enumerable, encryptor, prefix);
// Include only if contains decrypted values
if (indexed.containsDecrypted) {
decryptedProperties.putAll(indexed.values);
}
visitor.visited(indexed.values.keySet());
}
else {
// override previously encrypted with non-encrypted property
properties.remove(key);
var single = getPropertyValue(enumerable, encryptor, propertyName);
// Include only if decrypted
if (single.isDecrypted) {
decryptedProperties.put(propertyName, single.value);
}
visitor.visited(propertyName);
}
}
}
// copy all indexed properties even if not encrypted
if (sourceHasDecryptedCollection && !otherCollectionProperties.isEmpty()) {
properties.putAll(otherCollectionProperties);
}
}

return decryptedProperties;
}

private IndexedValue getPropertyValues(EnumerablePropertySource<?> source, TextEncryptor encryptor, String prefix) {
boolean containsDecrypted = false;
Map<String, Object> elements = new HashMap<>();
for (String name : source.getPropertyNames()) {
if (COLLECTION_PROPERTY.matcher(name).matches() && name.startsWith(prefix)) {
var value = getPropertyValue(source, encryptor, name);
elements.put(name, value.value);
if (value.isDecrypted) {
containsDecrypted = true;
}
}
}

return new IndexedValue(elements, containsDecrypted);
}

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;
private SingleValue getPropertyValue(PropertySource<?> source, TextEncryptor encryptor, String name) {
var value = source.getProperty(name);
if (value != null) {
var valueString = value.toString();
if (valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return new SingleValue(this.decrypt(encryptor, name, valueString), true);
}
return decrypt(encryptor, key, valueString);
});
}
return new SingleValue(value, false);
}

protected String decrypt(TextEncryptor encryptor, String key, String original) {
private String decrypt(TextEncryptor encryptor, String key, String original) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = encryptor.decrypt(value);
Expand All @@ -161,4 +155,38 @@ protected String decrypt(TextEncryptor encryptor, String key, String original) {
}
}

private record SingleValue(Object value, boolean isDecrypted) {
}

private record IndexedValue(Map<String, Object> values, boolean containsDecrypted) {
}

private static final class PropertyVisitor {

/**
* Using SystemEnvironmentPropertySource, instead of a simple Map, just to cover
* relaxed-binding cases.
* <p>
* See {@link SystemEnvironmentPropertySource#containsProperty(String) } for more
* details.
*/
private final SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource("visitor",
new HashMap<>());

boolean isVisited(String name) {
return this.propertySource.containsProperty(name);
}

void visited(String name) {
propertySource.getSource().put(name, "");
}

void visited(Set<String> names) {
for (String name : names) {
propertySource.getSource().put(name, "");
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp

MutablePropertySources propertySources = environment.getPropertySources();

environment.getPropertySources().remove(DECRYPTED_PROPERTY_SOURCE_NAME);

Map<String, Object> map = TextEncryptorUtils.decrypt(this, environment, propertySources);
Map<String, Object> map = TextEncryptorUtils.decrypt(this, environment);
if (!map.isEmpty()) {
// We have some decrypted properties
propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.cloud.bootstrap.encrypt;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -53,7 +52,7 @@ public class EnvironmentDecryptApplicationInitializer extends AbstractEnvironmen

private int order = Ordered.HIGHEST_PRECEDENCE + 15;

private TextEncryptor encryptor;
private final TextEncryptor encryptor;

public EnvironmentDecryptApplicationInitializer(TextEncryptor encryptor) {
this.encryptor = encryptor;
Expand Down Expand Up @@ -83,7 +82,9 @@ public void initialize(ConfigurableApplicationContext applicationContext) {
PropertySource<?> bootstrap = propertySources
.get(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME);
if (bootstrap != null) {
Map<String, Object> map = decrypt(bootstrap);
MutablePropertySources bootstrapSources = new MutablePropertySources();
bootstrapSources.addFirst(bootstrap);
Map<String, Object> map = decrypt(this.encryptor, bootstrapSources);
if (!map.isEmpty()) {
found.addAll(map.keySet());
insert(applicationContext,
Expand All @@ -92,7 +93,7 @@ public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
removeDecryptedProperties(applicationContext);
Map<String, Object> map = decrypt(this.encryptor, propertySources);
Map<String, Object> map = decrypt(this.encryptor, environment.getPropertySources());
if (!map.isEmpty()) {
// We have some decrypted properties
found.addAll(map.keySet());
Expand Down Expand Up @@ -145,16 +146,4 @@ private void removeDecryptedProperties(ApplicationContext applicationContext) {
}
}

private Map<String, Object> decrypt(PropertySource<?> source) {
Map<String, Object> properties = merge(source);
decrypt(this.encryptor, properties);
return properties;
}

private Map<String, Object> merge(PropertySource<?> source) {
Map<String, Object> properties = new LinkedHashMap<>();
merge(source, properties);
return properties;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
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.KeyStoreKeyFactory;
import org.springframework.security.crypto.encrypt.RsaSecretEncryptor;
import org.springframework.security.crypto.encrypt.TextEncryptor;
Expand All @@ -45,13 +44,12 @@ 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<String, Object> decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment,
MutablePropertySources propertySources) {
static Map<String, Object> decrypt(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) {
TextEncryptor encryptor = getTextEncryptor(decryptor, environment);
return decryptor.decrypt(encryptor, propertySources);

return decryptor.decrypt(encryptor, environment.getPropertySources());
}

static TextEncryptor getTextEncryptor(AbstractEnvironmentDecrypt decryptor, ConfigurableEnvironment environment) {
Expand Down
Loading
Loading