Allow additional keys to be configured for value sanitization

Closes gh-25384
This commit is contained in:
Andy Wilkinson 2021-02-24 19:28:29 +00:00
parent 10ef991e1d
commit f09630f73c
11 changed files with 106 additions and 15 deletions

View File

@ -49,6 +49,10 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration {
if (keysToSanitize != null) {
endpoint.setKeysToSanitize(keysToSanitize);
}
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
if (additionalKeysToSanitize != null) {
endpoint.keysToSanitize(additionalKeysToSanitize);
}
return endpoint;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-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.
@ -34,6 +34,12 @@ public class ConfigurationPropertiesReportEndpointProperties {
*/
private String[] keysToSanitize;
/**
* Keys that should be sanitized in addition to those already configured. Keys can be
* simple strings that the property ends with or regular expressions.
*/
private String[] additionalKeysToSanitize;
public String[] getKeysToSanitize() {
return this.keysToSanitize;
}
@ -42,4 +48,12 @@ public class ConfigurationPropertiesReportEndpointProperties {
this.keysToSanitize = keysToSanitize;
}
public String[] getAdditionalKeysToSanitize() {
return this.additionalKeysToSanitize;
}
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
this.additionalKeysToSanitize = additionalKeysToSanitize;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-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.
@ -47,6 +47,10 @@ public class EnvironmentEndpointAutoConfiguration {
if (keysToSanitize != null) {
endpoint.setKeysToSanitize(keysToSanitize);
}
String[] additionalKeysToSanitize = properties.getAdditionalKeysToSanitize();
if (additionalKeysToSanitize != null) {
endpoint.keysToSanitize(additionalKeysToSanitize);
}
return endpoint;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-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.
@ -34,6 +34,12 @@ public class EnvironmentEndpointProperties {
*/
private String[] keysToSanitize;
/**
* Keys that should be sanitized in addition to those already configured. Keys can be
* simple strings that the property ends with or regular expressions.
*/
private String[] additionalKeysToSanitize;
public String[] getKeysToSanitize() {
return this.keysToSanitize;
}
@ -42,4 +48,12 @@ public class EnvironmentEndpointProperties {
this.keysToSanitize = keysToSanitize;
}
public String[] getAdditionalKeysToSanitize() {
return this.additionalKeysToSanitize;
}
public void setAdditionalKeysToSanitize(String[] additionalKeysToSanitize) {
this.additionalKeysToSanitize = additionalKeysToSanitize;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-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.
@ -64,6 +64,14 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
.run(validateTestProperties("******", "******"));
}
@Test
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
this.contextRunner.withUserConfiguration(Config.class)
.withPropertyValues("management.endpoint.configprops.additional-keys-to-sanitize: property")
.withPropertyValues("management.endpoints.web.exposure.include=configprops")
.run(validateTestProperties("******", "******"));
}
@Test
void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-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.
@ -67,6 +67,14 @@ class EnvironmentEndpointAutoConfigurationTests {
.run(validateSystemProperties("******", "123456"));
}
@Test
void additionalKeysToSanitizeCanBeConfiguredViaTheEnvironment() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=env")
.withSystemProperties("dbPassword=123456", "apiKey=123456")
.withPropertyValues("management.endpoint.env.additional-keys-to-sanitize=key")
.run(validateSystemProperties("******", "******"));
}
private ContextConsumer<AssertableApplicationContext> validateSystemProperties(String dbPassword, String apiKey) {
return (context) -> {
assertThat(context).hasSingleBean(EnvironmentEndpoint.class);

View File

@ -115,6 +115,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
public void keysToSanitize(String... keysToSanitize) {
this.sanitizer.keysToSanitize(keysToSanitize);
}
@ReadOperation
public ApplicationConfigurationProperties configurationProperties() {
return extract(this.context, (bean) -> true);

View File

@ -67,8 +67,8 @@ public class Sanitizer {
}
/**
* Keys that should be sanitized. Keys can be simple strings that the property ends
* with or regular expressions.
* Set the keys that should be sanitized, overwriting any existing configuration. Keys
* can be simple strings that the property ends with or regular expressions.
* @param keysToSanitize the keys to sanitize
*/
public void setKeysToSanitize(String... keysToSanitize) {
@ -79,6 +79,21 @@ public class Sanitizer {
}
}
/**
* Adds keys that should be sanitized. Keys can be simple strings that the property
* ends with or regular expressions.
* @param keysToSanitize the keys to sanitize
* @since 2.5.0
*/
public void keysToSanitize(String... keysToSanitize) {
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
int existingKeys = this.keysToSanitize.length;
this.keysToSanitize = Arrays.copyOf(this.keysToSanitize, this.keysToSanitize.length + keysToSanitize.length);
for (int i = 0; i < keysToSanitize.length; i++) {
this.keysToSanitize[i + existingKeys] = getPattern(keysToSanitize[i]);
}
}
private Pattern getPattern(String value) {
if (isRegex(value)) {
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-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.
@ -76,6 +76,10 @@ public class EnvironmentEndpoint {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
public void keysToSanitize(String... keysToSanitize) {
this.sanitizer.keysToSanitize(keysToSanitize);
}
@ReadOperation
public EnvironmentDescriptor environment(@Nullable String pattern) {
if (StringUtils.hasText(pattern)) {

View File

@ -48,6 +48,23 @@ class SanitizerTests {
assertThat(sanitizer.sanitize("sun.java.command", "--spring.redis.password=pa55w0rd")).isEqualTo("******");
}
@Test
void whenAdditionalKeysAreAddedValuesOfBothThemAndTheDefaultKeysAreSanitized() {
Sanitizer sanitizer = new Sanitizer();
sanitizer.keysToSanitize("find", "confidential");
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-OTHER.paSSword", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somesecret", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("somekey", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("token", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("sun.java.command", "--spring.redis.password=pa55w0rd")).isEqualTo("******");
assertThat(sanitizer.sanitize("confidential", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("private", "secret")).isEqualTo("secret");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleValueWithPasswordShouldBeSanitized(String key) {

View File

@ -2333,14 +2333,10 @@ See also the section on "`<<spring-boot-features.adoc#boot-features-error-handli
[[howto-sanitize-sensible-values]]
[[howto-sanitize-sensitive-values]]
=== Sanitize Sensitive Values
Information returned by the `env` and `configprops` endpoints can be somewhat sensitive so keys matching a certain pattern are sanitized by default (i.e. their values are replaced by `+******+`).
Information returned by the `env` and `configprops` endpoints can be somewhat sensitive so keys matching certain patterns are sanitized by default (i.e. their values are replaced by `+******+`). Spring Boot uses sensible defaults for such keys: any key ending with the word "password", "secret", "key", "token", "vcap_services", "sun.java.command" is entirely sanitized.
Additionally, any key that holds the word `credentials` (configured as a regular expression, i.e. `+*credentials.*+`) as part of the key is also entirely sanitized.
The patterns to use can be customized using the `management.endpoint.env.keys-to-sanitize` and `management.endpoint.configprops.keys-to-sanitize` respectively.
Spring Boot uses sensible defaults for such keys: any key ending with the word "password", "secret", "key", "token", "vcap_services", "sun.java.command" is entirely sanitized.
Additionally, any key that holds the word `credentials` as part of the key is sanitized (configured as a regular expression, i.e. `+*credentials.*+`).
Furthermore, Spring Boot only sanitizes the sensitive portion of URI-like values for keys with one of the following endings:
Furthermore, Spring Boot sanitizes the sensitive portion of URI-like values for keys with one of the following endings:
- `address`
- `addresses`
@ -2353,6 +2349,9 @@ The sensitive portion of the URI is identified using the format `<scheme>://<use
For example, for the property `myclient.uri=http://user1:password1@localhost:8081`, the resulting sanitized value is
`++http://user1:******@localhost:8081++`.
The defaults patterns used by the `env` and `configprops` endpoints can be replaced using configprop:management.endpoint.env.keys-to-sanitize[] and configprop:management.endpoint.configprops.keys-to-sanitize[] respectively.
Alternatively, additional patterns can be configured using configprop:management.endpoint.env.additional-keys-to-sanitize[] and configprop:management.endpoint.configprops.additional-keys-to-sanitize[].
[[howto-map-health-indicators-to-metrics]]