diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java index 416cd0be9be9..a38153ad4ed8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java @@ -30,6 +30,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Piotr Betkier * @since 2.0.0 */ @Configuration @@ -47,12 +48,11 @@ public ConfigurationPropertiesReportEndpointAutoConfiguration( @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() { - ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(); String[] keysToSanitize = this.properties.getKeysToSanitize(); if (keysToSanitize != null) { - endpoint.setKeysToSanitize(keysToSanitize); + return new ConfigurationPropertiesReportEndpoint(keysToSanitize); } - return endpoint; + return new ConfigurationPropertiesReportEndpoint(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java index a378d46b23f6..b38f325dbd0c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java @@ -16,7 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.env; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.endpoint.sanitize.KeyBlacklistSanitizeFilter; import org.springframework.boot.actuate.env.EnvironmentEndpoint; import org.springframework.boot.actuate.env.EnvironmentEndpointWebExtension; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -32,6 +38,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Piotr Betkier * @since 2.0.0 */ @Configuration @@ -48,13 +55,19 @@ public EnvironmentEndpointAutoConfiguration( @Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint - public EnvironmentEndpoint environmentEndpoint(Environment environment) { - EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment); + public EnvironmentEndpoint environmentEndpoint(Environment environment, + ObjectProvider> customFilters) { + String[] keysToSanitize = this.properties.getKeysToSanitize(); - if (keysToSanitize != null) { - endpoint.setKeysToSanitize(keysToSanitize); - } - return endpoint; + KeyBlacklistSanitizeFilter keyMatchFilter = (keysToSanitize != null) + ? KeyBlacklistSanitizeFilter.matchingKeys(keysToSanitize) + : KeyBlacklistSanitizeFilter.matchingDefaultKeys(); + + Collection filters = new ArrayList<>(); + filters.add(EnvironmentEndpoint.SanitizeFilter.from(keyMatchFilter)); + Optional.ofNullable(customFilters.getIfAvailable()).ifPresent(filters::addAll); + + return new EnvironmentEndpoint(environment, filters); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java index 9e4a17521267..fb96dd7de144 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfigurationTests.java @@ -28,6 +28,8 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -35,17 +37,18 @@ * Tests for {@link EnvironmentEndpointAutoConfiguration}. * * @author Phillip Webb + * @author Piotr Betkier */ public class EnvironmentEndpointAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(EnvironmentEndpointAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = createContextRunner( + EnvironmentEndpointAutoConfiguration.class); @Test public void runShouldHaveEndpointBean() { - this.contextRunner.withSystemProperties("dbPassword=123456", "apiKey=123456") - .run(validateSystemProperties("******", "******")); + this.contextRunner + .withSystemProperties("dbPassword=123456", "apiKey=123456", "fine=123456") + .run(validateSystemProperties("******", "******", "123456")); } @Test @@ -58,13 +61,29 @@ public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() @Test public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception { - this.contextRunner.withSystemProperties("dbPassword=123456", "apiKey=123456") + this.contextRunner + .withSystemProperties("dbPassword=123456", "apiKey=123456", "fine=123456") .withPropertyValues("management.endpoint.env.keys-to-sanitize=.*pass.*") - .run(validateSystemProperties("******", "123456")); + .run(validateSystemProperties("******", "123456", "123456")); + } + + @Test + public void customSanitizationRulesCanBeConfiguredByRegisteringFilters() + throws Exception { + createContextRunner(EnvironmentEndpointAutoConfiguration.class, + EnvironmentEndpointSanitizerConfiguration.class) + .withSystemProperties("dbPassword=123456", "apiKey=123456", + "fine=123456") + .run(validateSystemProperties("******", "******", "******")); + } + + private ApplicationContextRunner createContextRunner(Class... configurations) { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(configurations)); } private ContextConsumer validateSystemProperties( - String dbPassword, String apiKey) { + String dbPassword, String apiKey, String fine) { return (context) -> { assertThat(context).hasSingleBean(EnvironmentEndpoint.class); EnvironmentEndpoint endpoint = context.getBean(EnvironmentEndpoint.class); @@ -74,6 +93,7 @@ private ContextConsumer validateSystemProperties( assertThat(systemProperties.get("dbPassword").getValue()) .isEqualTo(dbPassword); assertThat(systemProperties.get("apiKey").getValue()).isEqualTo(apiKey); + assertThat(systemProperties.get("fine").getValue()).isEqualTo(fine); }; } @@ -83,4 +103,14 @@ private PropertySourceDescriptor getSource(String name, .filter((source) -> name.equals(source.getName())).findFirst().get(); } + @Configuration + public static class EnvironmentEndpointSanitizerConfiguration { + + @Bean + public EnvironmentEndpoint.SanitizeFilter systemPropertiesSanitizeFilter() { + return s -> s.getSource().getName().equals("systemProperties"); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 818487264de8..97393d95805d 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -44,9 +44,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; -import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.sanitize.Sanitizer; import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -74,7 +74,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext private static final String CONFIGURATION_PROPERTIES_FILTER_ID = "configurationPropertiesFilter"; - private final Sanitizer sanitizer = new Sanitizer(); + private final Sanitizer sanitizer; private ApplicationContext context; @@ -83,8 +83,12 @@ public void setApplicationContext(ApplicationContext context) throws BeansExcept this.context = context; } - public void setKeysToSanitize(String... keysToSanitize) { - this.sanitizer.setKeysToSanitize(keysToSanitize); + public ConfigurationPropertiesReportEndpoint() { + this.sanitizer = Sanitizer.keyBlacklistSanitizer(); + } + + public ConfigurationPropertiesReportEndpoint(String... keysToSanitize) { + this.sanitizer = Sanitizer.keyBlacklistSanitizer(keysToSanitize); } @ReadOperation diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/KeyBlacklistSanitizeFilter.java similarity index 51% rename from spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java rename to spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/KeyBlacklistSanitizeFilter.java index 5d5df2fef0d7..8c24720e5eee 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/KeyBlacklistSanitizeFilter.java @@ -14,58 +14,60 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint; +package org.springframework.boot.actuate.endpoint.sanitize; import java.util.regex.Pattern; import org.springframework.util.Assert; /** - * Strategy that should be used by endpoint implementations to sanitize potentially - * sensitive keys. + * Sanitize filter operating on String keys. Matches if the given key contains any of the + * configured words. Supports basic regex syntax. * - * @author Christian Dupuis - * @author Toshiaki Maki - * @author Phillip Webb - * @author Nicolas Lejeune - * @author Stephane Nicoll + * @author Piotr Betkier * @since 2.0.0 */ -public class Sanitizer { +public final class KeyBlacklistSanitizeFilter implements Sanitizer.Filter { private static final String[] REGEX_PARTS = { "*", "$", "^", "+" }; - private Pattern[] keysToSanitize; + private final Pattern[] keysToSanitize; - public Sanitizer() { - this("password", "secret", "key", "token", ".*credentials.*", "vcap_services"); + private KeyBlacklistSanitizeFilter(Pattern[] patterns) { + this.keysToSanitize = patterns; } - public Sanitizer(String... keysToSanitize) { - setKeysToSanitize(keysToSanitize); + /** + * Creates the filter based on the default blacklist. + * @return a new filter instance + */ + public static KeyBlacklistSanitizeFilter matchingDefaultKeys() { + return KeyBlacklistSanitizeFilter.matchingKeys("password", "secret", "key", + "token", ".*credentials.*", "vcap_services"); } /** - * Keys that should be sanitized. Keys can be simple strings that the property ends - * with or regular expressions. - * @param keysToSanitize the keys to sanitize + * Creates the filter based on the given blacklist. + * @param keysToSanitize keys to match + * @return a new filter instance */ - public void setKeysToSanitize(String... keysToSanitize) { + public static KeyBlacklistSanitizeFilter matchingKeys(String... keysToSanitize) { Assert.notNull(keysToSanitize, "KeysToSanitize must not be null"); - this.keysToSanitize = new Pattern[keysToSanitize.length]; + Pattern[] patterns = new Pattern[keysToSanitize.length]; for (int i = 0; i < keysToSanitize.length; i++) { - this.keysToSanitize[i] = getPattern(keysToSanitize[i]); + patterns[i] = getPattern(keysToSanitize[i]); } + return new KeyBlacklistSanitizeFilter(patterns); } - private Pattern getPattern(String value) { + private static Pattern getPattern(String value) { if (isRegex(value)) { return Pattern.compile(value, Pattern.CASE_INSENSITIVE); } return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE); } - private boolean isRegex(String value) { + private static boolean isRegex(String value) { for (String part : REGEX_PARTS) { if (value.contains(part)) { return true; @@ -74,22 +76,13 @@ private boolean isRegex(String value) { return false; } - /** - * Sanitize the given value if necessary. - * @param key the key to sanitize - * @param value the value - * @return the potentially sanitized value - */ - public Object sanitize(String key, Object value) { - if (value == null) { - return null; - } + @Override + public boolean match(String key) { for (Pattern pattern : this.keysToSanitize) { if (pattern.matcher(key).matches()) { - return "******"; + return true; } } - return value; + return false; } - } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/Sanitizer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/Sanitizer.java new file mode 100644 index 000000000000..92aadd3acb58 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/sanitize/Sanitizer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.sanitize; + +import java.util.Collection; +import java.util.Collections; + +/** + * Strategy that should be used by endpoint implementations to sanitize potentially + * sensitive keys. + * + * @param the key type + * @author Christian Dupuis + * @author Toshiaki Maki + * @author Phillip Webb + * @author Nicolas Lejeune + * @author Stephane Nicoll + * @author Piotr Betkier + * @since 2.0.0 + */ +public final class Sanitizer { + + private final Collection> filters; + + private Sanitizer(Collection> filters) { + this.filters = filters; + } + + /** + * Creates a sanitizer that sanitizes keys matching the default blacklist. + * @return a new sanitizer instance + */ + public static Sanitizer keyBlacklistSanitizer() { + return new Sanitizer<>( + Collections.singleton(KeyBlacklistSanitizeFilter.matchingDefaultKeys())); + } + + /** + * Creates a sanitizer that sanitizes keys matching the given blacklist. + * @param keysToSanitize the keys to match + * @return a new sanitizer instance + */ + public static Sanitizer keyBlacklistSanitizer(String... keysToSanitize) { + return new Sanitizer<>(Collections + .singleton(KeyBlacklistSanitizeFilter.matchingKeys(keysToSanitize))); + } + + /** + * Creates a sanitizer that sanitizes keys matched by any of the given filters. + * @param filters the filters to use + * @param the key type + * @return a new sanitizer instance + */ + public static Sanitizer customRulesSanitizer(Collection> filters) { + return new Sanitizer<>(filters); + } + + /** + * Sanitize the given value if necessary. + * @param key the value key, e.g. property name + * @param value the value + * @return the potentially sanitized value + */ + public Object sanitize(T key, Object value) { + if (value == null) { + return null; + } + + if (this.filters.stream().anyMatch(f -> f.match(key))) { + return "******"; + } + + return value; + } + + /** + * Should the value for the given key be sanitized. + * @param the key type + */ + public interface Filter { + boolean match(T key); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java index c3435408ccd6..341fe18723bc 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java @@ -18,19 +18,23 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonInclude; -import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.sanitize.KeyBlacklistSanitizeFilter; +import org.springframework.boot.actuate.endpoint.sanitize.Sanitizer; import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; @@ -55,21 +59,27 @@ * @author Christian Dupuis * @author Madhura Bhave * @author Stephane Nicoll + * @author Piotr Betkier * @since 2.0.0 */ @Endpoint(id = "env") public class EnvironmentEndpoint { - private final Sanitizer sanitizer = new Sanitizer(); + private final Sanitizer sanitizer; private final Environment environment; public EnvironmentEndpoint(Environment environment) { - this.environment = environment; + this(environment, Collections.singleton(SanitizeFilter + .from(KeyBlacklistSanitizeFilter.matchingDefaultKeys()))); } - public void setKeysToSanitize(String... keysToSanitize) { - this.sanitizer.setKeysToSanitize(keysToSanitize); + public EnvironmentEndpoint(Environment environment, + Collection filters) { + this.environment = environment; + this.sanitizer = Sanitizer.customRulesSanitizer( + filters.stream().map(f -> (Sanitizer.Filter) f) + .collect(Collectors.toList())); } @ReadOperation @@ -156,7 +166,8 @@ private PropertyValueDescriptor describeValueOf(String name, PropertySource s Object resolved = resolver.resolvePlaceholders(source.getProperty(name)); String origin = (source instanceof OriginLookup) ? ((OriginLookup) source).getOrigin(name).toString() : null; - return new PropertyValueDescriptor(sanitize(name, resolved), origin); + Object sanitizedValue = sanitize(new PropertyNameAndSource(name, source), resolved); + return new PropertyValueDescriptor(sanitizedValue, origin); } private PlaceholdersResolver getResolver() { @@ -195,8 +206,8 @@ private void extract(String root, Map> map, } } - public Object sanitize(String name, Object object) { - return this.sanitizer.sanitize(name, object); + private Object sanitize(PropertyNameAndSource source, Object object) { + return this.sanitizer.sanitize(source, object); } /** @@ -206,10 +217,10 @@ public Object sanitize(String name, Object object) { private static class PropertySourcesPlaceholdersSanitizingResolver extends PropertySourcesPlaceholdersResolver { - private final Sanitizer sanitizer; + private final Sanitizer sanitizer; PropertySourcesPlaceholdersSanitizingResolver(Iterable> sources, - Sanitizer sanitizer) { + Sanitizer sanitizer) { super(sources, new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX, @@ -218,14 +229,11 @@ private static class PropertySourcesPlaceholdersSanitizingResolver } @Override - protected String resolvePlaceholder(String placeholder) { - String value = super.resolvePlaceholder(placeholder); - if (value == null) { - return null; - } - return (String) this.sanitizer.sanitize(placeholder, value); + protected String convertResolvedValueToString(String placeholder, Object value, + PropertySource source) { + return (String) this.sanitizer + .sanitize(new PropertyNameAndSource(placeholder, source), value); } - } /** @@ -388,4 +396,38 @@ public String getOrigin() { } + /** + * Property name and its source. + */ + public static final class PropertyNameAndSource { + + private final String name; + + private final PropertySource source; + + private PropertyNameAndSource(String name, PropertySource source) { + this.name = name; + this.source = source; + } + + public String getName() { + return this.name; + } + + public PropertySource getSource() { + return this.source; + } + } + + /** + * Implement this interface to customize the sanitization rules. + */ + public interface SanitizeFilter extends Sanitizer.Filter { + + static SanitizeFilter from(Sanitizer.Filter propertyNameBasedFilter) { + return nameAndSource -> propertyNameBasedFilter.match(nameAndSource.getName()); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index 1f0d9d3ee121..a32205b02b1e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -175,12 +175,10 @@ private void load(List keysToSanitize, ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withUserConfiguration(Config.class); contextRunner.run((context) -> { - ConfigurationPropertiesReportEndpoint endpoint = context - .getBean(ConfigurationPropertiesReportEndpoint.class); - if (!CollectionUtils.isEmpty(keysToSanitize)) { - endpoint.setKeysToSanitize( - keysToSanitize.toArray(new String[keysToSanitize.size()])); - } + ConfigurationPropertiesReportEndpointFactory endpointFactory = context + .getBean(ConfigurationPropertiesReportEndpointFactory.class); + ConfigurationPropertiesReportEndpoint endpoint = endpointFactory + .create(context, keysToSanitize); properties.accept(context, endpoint.configurationProperties()); }); } @@ -196,13 +194,31 @@ public TestProperties testProperties() { } + private static class ConfigurationPropertiesReportEndpointFactory { + + ConfigurationPropertiesReportEndpoint create(ApplicationContext context, + List keysToSanitize) { + ConfigurationPropertiesReportEndpoint endpoint; + if (!CollectionUtils.isEmpty(keysToSanitize)) { + endpoint = new ConfigurationPropertiesReportEndpoint( + keysToSanitize.toArray(new String[keysToSanitize.size()])); + } + else { + endpoint = new ConfigurationPropertiesReportEndpoint(); + } + endpoint.setApplicationContext(context); + return endpoint; + } + + } + @Configuration @EnableConfigurationProperties public static class Config { @Bean - public ConfigurationPropertiesReportEndpoint endpoint() { - return new ConfigurationPropertiesReportEndpoint(); + public ConfigurationPropertiesReportEndpointFactory factory() { + return new ConfigurationPropertiesReportEndpointFactory(); } @Bean diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SanitizerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SanitizerTests.java index b9a1a3d13b81..6affccb3299b 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SanitizerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SanitizerTests.java @@ -16,8 +16,12 @@ package org.springframework.boot.actuate.endpoint; +import java.util.Collections; + import org.junit.Test; +import org.springframework.boot.actuate.endpoint.sanitize.Sanitizer; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -25,12 +29,13 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Piotr Betkier */ public class SanitizerTests { @Test public void defaults() throws Exception { - Sanitizer sanitizer = new Sanitizer(); + Sanitizer sanitizer = Sanitizer.keyBlacklistSanitizer(); assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******"); @@ -43,9 +48,30 @@ public void defaults() throws Exception { @Test public void regex() throws Exception { - Sanitizer sanitizer = new Sanitizer(".*lock.*"); + Sanitizer sanitizer = Sanitizer.keyBlacklistSanitizer(".*lock.*"); assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret"); } + @Test + public void customFilter() throws Exception { + Sanitizer.Filter secretSourceFilter = s -> s.data + .contains("secret"); + Sanitizer sanitizer = Sanitizer + .customRulesSanitizer(Collections.singleton(secretSourceFilter)); + + assertThat(sanitizer.sanitize(new CustomSource("secretSource"), + "should-be-sanitized")).isEqualTo("******"); + assertThat(sanitizer.sanitize(new CustomSource("ordinarySource"), + "should-not-be-sanitized")).isEqualTo("should-not-be-sanitized"); + } + + private static final class CustomSource { + + private final String data; + + private CustomSource(String data) { + this.data = data; + } + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java index 166248552332..3a12275b68d2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/env/EnvironmentEndpointTests.java @@ -23,6 +23,7 @@ import org.junit.After; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.sanitize.KeyBlacklistSanitizeFilter; import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor; import org.springframework.boot.actuate.env.EnvironmentEndpoint.PropertySourceDescriptor; @@ -145,9 +146,12 @@ public void sensitiveKeysMatchingCredentialsPatternHaveTheirValuesSanitized() { public void sensitiveKeysMatchingCustomNameHaveTheirValuesSanitized() { TestPropertyValues.of("dbPassword=123456", "apiKey=123456") .applyToSystemProperties(() -> { + KeyBlacklistSanitizeFilter filter = + KeyBlacklistSanitizeFilter.matchingKeys("key"); EnvironmentEndpoint endpoint = new EnvironmentEndpoint( - new StandardEnvironment()); - endpoint.setKeysToSanitize("key"); + new StandardEnvironment(), + Collections.singleton(EnvironmentEndpoint.SanitizeFilter.from(filter)) + ); EnvironmentDescriptor descriptor = endpoint.environment(null); Map systemProperties = propertySources( descriptor).get("systemProperties").getProperties(); @@ -163,9 +167,12 @@ public void sensitiveKeysMatchingCustomNameHaveTheirValuesSanitized() { public void sensitiveKeysMatchingCustomPatternHaveTheirValuesSanitized() { TestPropertyValues.of("dbPassword=123456", "apiKey=123456") .applyToSystemProperties(() -> { + KeyBlacklistSanitizeFilter filter = + KeyBlacklistSanitizeFilter.matchingKeys(".*pass.*"); EnvironmentEndpoint endpoint = new EnvironmentEndpoint( - new StandardEnvironment()); - endpoint.setKeysToSanitize(".*pass.*"); + new StandardEnvironment(), + Collections.singleton(EnvironmentEndpoint.SanitizeFilter.from(filter)) + ); EnvironmentDescriptor descriptor = endpoint.environment(null); Map systemProperties = propertySources( descriptor).get("systemProperties").getProperties(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java index 3dde32943a07..d0eef860068c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java @@ -29,6 +29,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Piotr Betkier * @since 2.0.0 */ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver { @@ -63,18 +64,22 @@ public Object resolvePlaceholders(Object value) { return value; } - protected String resolvePlaceholder(String placeholder) { + private String resolvePlaceholder(String placeholder) { if (this.sources != null) { for (PropertySource source : this.sources) { Object value = source.getProperty(placeholder); if (value != null) { - return String.valueOf(value); + return convertResolvedValueToString(placeholder, value, source); } } } return null; } + protected String convertResolvedValueToString(String placeholder, Object value, PropertySource source) { + return String.valueOf(value); + } + private static PropertySources getSources(Environment environment) { Assert.notNull(environment, "Environment must not be null"); Assert.isInstanceOf(ConfigurableEnvironment.class, environment,