Skip to content

Allow customizing sanitization rules for /env #11264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
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 @@ -30,6 +30,7 @@
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Piotr Betkier
* @since 2.0.0
*/
@Configuration
Expand All @@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +38,7 @@
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Piotr Betkier
* @since 2.0.0
*/
@Configuration
Expand All @@ -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<Collection<EnvironmentEndpoint.SanitizeFilter>> customFilters) {

String[] keysToSanitize = this.properties.getKeysToSanitize();
if (keysToSanitize != null) {
endpoint.setKeysToSanitize(keysToSanitize);
}
return endpoint;
KeyBlacklistSanitizeFilter keyMatchFilter = (keysToSanitize != null)
? KeyBlacklistSanitizeFilter.matchingKeys(keysToSanitize)
: KeyBlacklistSanitizeFilter.matchingDefaultKeys();
Copy link
Author

@pbetkier pbetkier Dec 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to create the instance of KeyBlacklistSanitizeFilter as a bean that we would autowire here, but couldn't express the condition "register the filter bean only if environment endpoint is enabled". Is there a way for expressing that given that the filter bean must be registered before EnvironmentEndpoint?


Collection<EnvironmentEndpoint.SanitizeFilter> filters = new ArrayList<>();
filters.add(EnvironmentEndpoint.SanitizeFilter.from(keyMatchFilter));
Optional.ofNullable(customFilters.getIfAvailable()).ifPresent(filters::addAll);

return new EnvironmentEndpoint(environment, filters);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,27 @@
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;

/**
* 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
Expand All @@ -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<AssertableApplicationContext> validateSystemProperties(
String dbPassword, String apiKey) {
String dbPassword, String apiKey, String fine) {
return (context) -> {
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);
EnvironmentEndpoint endpoint = context.getBean(EnvironmentEndpoint.class);
Expand All @@ -74,6 +93,7 @@ private ContextConsumer<AssertableApplicationContext> validateSystemProperties(
assertThat(systemProperties.get("dbPassword").getValue())
.isEqualTo(dbPassword);
assertThat(systemProperties.get("apiKey").getValue()).isEqualTo(apiKey);
assertThat(systemProperties.get("fine").getValue()).isEqualTo(fine);
};
}

Expand All @@ -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");
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> sanitizer;

private ApplicationContext context;

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {

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;
Expand All @@ -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;
}

}
Loading