Commit badc83d3 authored by cbono's avatar cbono Committed by Madhura Bhave

Add 'uris', 'address' and 'addresses' to keys to sanitize.

See gh-19999
parent 45fd6033
...@@ -16,8 +16,10 @@ ...@@ -16,8 +16,10 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import java.util.Arrays;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -32,18 +34,23 @@ import org.springframework.util.StringUtils; ...@@ -32,18 +34,23 @@ import org.springframework.util.StringUtils;
* @author Nicolas Lejeune * @author Nicolas Lejeune
* @author Stephane Nicoll * @author Stephane Nicoll
* @author HaiTao Zhang * @author HaiTao Zhang
* @author Chris Bono
* @since 2.0.0 * @since 2.0.0
*/ */
public class Sanitizer { public class Sanitizer {
private static final String[] REGEX_PARTS = { "*", "$", "^", "+" }; private static final String[] REGEX_PARTS = { "*", "$", "^", "+" };
private static final String[] DEFAULT_KEYS_TO_SANITIZE = { "password", "secret", "key", "token", ".*credentials.*", "vcap_services", "sun.java.command", "uri", "uris", "address", "addresses" };
private static final String[] URI_USERINFO_KEYS = { "uri", "uris", "address", "addresses" };
private static final Pattern URI_USERINFO_PATTERN = Pattern.compile("[A-Za-z]+://.+:(.*)@.+$"); private static final Pattern URI_USERINFO_PATTERN = Pattern.compile("[A-Za-z]+://.+:(.*)@.+$");
private Pattern[] keysToSanitize; private Pattern[] keysToSanitize;
public Sanitizer() { public Sanitizer() {
this("password", "secret", "key", "token", ".*credentials.*", "vcap_services", "sun.java.command", "uri"); this(DEFAULT_KEYS_TO_SANITIZE);
} }
public Sanitizer(String... keysToSanitize) { public Sanitizer(String... keysToSanitize) {
...@@ -91,8 +98,8 @@ public class Sanitizer { ...@@ -91,8 +98,8 @@ public class Sanitizer {
} }
for (Pattern pattern : this.keysToSanitize) { for (Pattern pattern : this.keysToSanitize) {
if (pattern.matcher(key).matches()) { if (pattern.matcher(key).matches()) {
if (pattern.matcher("uri").matches()) { if (keyIsUriWithUserInfo(pattern)) {
return sanitizeUri(value); return sanitizeUris(value.toString());
} }
return "******"; return "******";
} }
...@@ -100,14 +107,28 @@ public class Sanitizer { ...@@ -100,14 +107,28 @@ public class Sanitizer {
return value; return value;
} }
private Object sanitizeUri(Object value) { private boolean keyIsUriWithUserInfo(Pattern pattern) {
String uriString = value.toString(); for (String uriKey : URI_USERINFO_KEYS) {
if (pattern.matcher(uriKey).matches()) {
return true;
}
}
return false;
}
private Object sanitizeUris(String uriString) {
// Treat each uri value as possibly containing multiple uris (comma separated)
return Arrays.stream(uriString.split(","))
.map(this::sanitizeUri)
.collect(Collectors.joining(","));
}
private String sanitizeUri(String uriString) {
Matcher matcher = URI_USERINFO_PATTERN.matcher(uriString); Matcher matcher = URI_USERINFO_PATTERN.matcher(uriString);
String password = matcher.matches() ? matcher.group(1) : null; String password = matcher.matches() ? matcher.group(1) : null;
if (password != null) { if (password != null) {
return StringUtils.replace(uriString, ":" + password + "@", ":******@"); return StringUtils.replace(uriString, ":" + password + "@", ":******@");
} }
return value; return uriString;
} }
} }
...@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.context.properties; ...@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.context.properties;
import java.net.URI; import java.net.URI;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -51,6 +52,7 @@ import static org.assertj.core.api.Assertions.entry; ...@@ -51,6 +52,7 @@ import static org.assertj.core.api.Assertions.entry;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author HaiTao Zhang * @author HaiTao Zhang
* @author Chris Bono
*/ */
class ConfigurationPropertiesReportEndpointTests { class ConfigurationPropertiesReportEndpointTests {
...@@ -170,19 +172,26 @@ class ConfigurationPropertiesReportEndpointTests { ...@@ -170,19 +172,26 @@ class ConfigurationPropertiesReportEndpointTests {
} }
@Test @Test
void sanitizedUriWithSensitiveInfo() { void sanitizeUriWithSensitiveInfo() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class) this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("sensitiveUri")) .run(assertProperties("sensible", (properties) -> assertThat(properties.get("sensitiveUri"))
.isEqualTo("http://user:******@localhost:8080"))); .isEqualTo("http://user:******@localhost:8080")));
} }
@Test @Test
void sanitizedUriWithNoPassword() { void sanitizeUriWithNoPassword() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class) this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("noPasswordUri")) .run(assertProperties("sensible", (properties) -> assertThat(properties.get("noPasswordUri"))
.isEqualTo("http://user:******@localhost:8080"))); .isEqualTo("http://user:******@localhost:8080")));
} }
@Test
void sanitizeAddressesFieldContainingMultipleRawSensitiveUris() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("rawSensitiveAddresses"))
.isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082")));
}
@Test @Test
void sanitizeLists() { void sanitizeLists() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class) this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
...@@ -574,6 +583,8 @@ class ConfigurationPropertiesReportEndpointTests { ...@@ -574,6 +583,8 @@ class ConfigurationPropertiesReportEndpointTests {
private URI noPasswordUri = URI.create("http://user:@localhost:8080"); private URI noPasswordUri = URI.create("http://user:@localhost:8080");
private String rawSensitiveAddresses = "http://user:password@localhost:8080,http://user2:password2@localhost:8082";
private List<ListItem> listItems = new ArrayList<>(); private List<ListItem> listItems = new ArrayList<>();
private List<List<ListItem>> listOfListItems = new ArrayList<>(); private List<List<ListItem>> listOfListItems = new ArrayList<>();
...@@ -599,6 +610,14 @@ class ConfigurationPropertiesReportEndpointTests { ...@@ -599,6 +610,14 @@ class ConfigurationPropertiesReportEndpointTests {
return this.noPasswordUri; return this.noPasswordUri;
} }
public String getRawSensitiveAddresses() {
return this.rawSensitiveAddresses;
}
public void setRawSensitiveAddresses(final String rawSensitiveAddresses) {
this.rawSensitiveAddresses = rawSensitiveAddresses;
}
public List<ListItem> getListItems() { public List<ListItem> getListItems() {
return this.listItems; return this.listItems;
} }
......
...@@ -17,6 +17,10 @@ ...@@ -17,6 +17,10 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -25,11 +29,12 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -25,11 +29,12 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Chris Bono
*/ */
class SanitizerTests { class SanitizerTests {
@Test @Test
void defaults() { void defaultNonUriKeys() {
Sanitizer sanitizer = new Sanitizer(); Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
...@@ -40,21 +45,64 @@ class SanitizerTests { ...@@ -40,21 +45,64 @@ class SanitizerTests {
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("secret"); assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("secret");
assertThat(sanitizer.sanitize("sun.java.command", "--spring.redis.password=pa55w0rd")).isEqualTo("******"); assertThat(sanitizer.sanitize("sun.java.command", "--spring.redis.password=pa55w0rd")).isEqualTo("******");
assertThat(sanitizer.sanitize("my.uri", "http://user:password@localhost:8080"))
.isEqualTo("http://user:******@localhost:8080");
} }
@Test @ParameterizedTest(name = "key = {0}")
void uriWithNoPasswordShouldNotBeSanitized() { @MethodSource("matchingUriUserInfoKeys")
void uriWithSingleEntryWithPasswordShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer(); Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize("my.uri", "http://localhost:8080")).isEqualTo("http://localhost:8080"); assertThat(sanitizer.sanitize(key, "http://user:password@localhost:8080")).isEqualTo("http://user:******@localhost:8080");
} }
@Test @ParameterizedTest(name = "key = {0}")
void uriWithPasswordMatchingOtherPartsOfString() { @MethodSource("matchingUriUserInfoKeys")
void uriWithSingleEntryWithNoPasswordShouldNotBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://localhost:8080")).isEqualTo("http://localhost:8080");
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080")).isEqualTo("http://user@localhost:8080");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleEntryWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer(); Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize("my.uri", "http://user://@localhost:8080")) assertThat(sanitizer.sanitize(key, "http://user://@localhost:8080")).isEqualTo("http://user:******@localhost:8080");
.isEqualTo("http://user:******@localhost:8080"); }
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleEntriesEachWithPasswordShouldHaveAllSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user1:password1@localhost:8080,http://user2:password2@localhost:8082"))
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleEntriesNoneWithPasswordShouldHaveNoneSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user@localhost:8080,http://localhost:8082"))
.isEqualTo("http://user@localhost:8080,http://localhost:8082");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleEntriesSomeWithPasswordShouldHaveThoseSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user1:password1@localhost:8080,http://user2@localhost:8082,http://localhost:8083"))
.isEqualTo("http://user1:******@localhost:8080,http://user2@localhost:8082,http://localhost:8083");
}
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithMultipleEntriesWithPasswordMatchingOtherPartsOfStringShouldBeSanitized(String key) {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize(key, "http://user1://@localhost:8080,http://user2://@localhost:8082"))
.isEqualTo("http://user1:******@localhost:8080,http://user2:******@localhost:8082");
}
static private Stream<String> matchingUriUserInfoKeys() {
return Stream.of("uri", "my.uri", "myuri", "uris", "my.uris", "myuris", "address", "my.address", "myaddress", "addresses", "my.addresses", "myaddresses");
} }
@Test @Test
...@@ -63,5 +111,4 @@ class SanitizerTests { ...@@ -63,5 +111,4 @@ class SanitizerTests {
assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******"); assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret"); assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret");
} }
} }
...@@ -50,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -50,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Madhura Bhave * @author Madhura Bhave
* @author Andy Wilkinson * @author Andy Wilkinson
* @author HaiTao Zhang * @author HaiTao Zhang
* @author Chris Bono
*/ */
class EnvironmentEndpointTests { class EnvironmentEndpointTests {
...@@ -246,13 +247,21 @@ class EnvironmentEndpointTests { ...@@ -246,13 +247,21 @@ class EnvironmentEndpointTests {
} }
@Test @Test
void uriPropertryWithSensitiveInfo() { void uriPropertyWithSensitiveInfo() {
ConfigurableEnvironment environment = new StandardEnvironment(); ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("sensitive.uri=http://user:password@localhost:8080").applyTo(environment); TestPropertyValues.of("sensitive.uri=http://user:password@localhost:8080").applyTo(environment);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.uri"); EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.uri");
assertThat(descriptor.getProperty().getValue()).isEqualTo("http://user:******@localhost:8080"); assertThat(descriptor.getProperty().getValue()).isEqualTo("http://user:******@localhost:8080");
} }
@Test
void addressesPropertyWithMultipleEntriesEachWithSensitiveInfo() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("sensitive.addresses=http://user:password@localhost:8080,http://user2:password2@localhost:8082").applyTo(environment);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.addresses");
assertThat(descriptor.getProperty().getValue()).isEqualTo("http://user:******@localhost:8080,http://user2:******@localhost:8082");
}
private static ConfigurableEnvironment emptyEnvironment() { private static ConfigurableEnvironment emptyEnvironment() {
StandardEnvironment environment = new StandardEnvironment(); StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment