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,8 +16,10 @@

package org.springframework.boot.actuate.endpoint;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand All @@ -32,18 +34,23 @@
* @author Nicolas Lejeune
* @author Stephane Nicoll
* @author HaiTao Zhang
* @author Chris Bono
* @since 2.0.0
*/
public class Sanitizer {

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 Pattern[] keysToSanitize;

public Sanitizer() {
this("password", "secret", "key", "token", ".*credentials.*", "vcap_services", "sun.java.command", "uri");
this(DEFAULT_KEYS_TO_SANITIZE);
}

public Sanitizer(String... keysToSanitize) {
Expand Down Expand Up @@ -91,23 +98,37 @@ public Object sanitize(String key, Object value) {
}
for (Pattern pattern : this.keysToSanitize) {
if (pattern.matcher(key).matches()) {
if (pattern.matcher("uri").matches()) {
return sanitizeUri(value);
if (keyIsUriWithUserInfo(pattern)) {
return sanitizeUris(value.toString());
}
return "******";
}
}
return value;
}

private Object sanitizeUri(Object value) {
String uriString = value.toString();
private boolean keyIsUriWithUserInfo(Pattern pattern) {
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);
String password = matcher.matches() ? matcher.group(1) : null;
if (password != null) {
return StringUtils.replace(uriString, ":" + password + "@", ":******@");
}
return value;
return uriString;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -51,6 +52,7 @@
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author HaiTao Zhang
* @author Chris Bono
*/
class ConfigurationPropertiesReportEndpointTests {

Expand Down Expand Up @@ -170,19 +172,26 @@ void sanitizeWithCustomPatternUsingCompositeKeys() {
}

@Test
void sanitizedUriWithSensitiveInfo() {
void sanitizeUriWithSensitiveInfo() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("sensitiveUri"))
.isEqualTo("http://user:******@localhost:8080")));
}

@Test
void sanitizedUriWithNoPassword() {
void sanitizeUriWithNoPassword() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
.run(assertProperties("sensible", (properties) -> assertThat(properties.get("noPasswordUri"))
.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
void sanitizeLists() {
this.contextRunner.withUserConfiguration(SensiblePropertiesConfiguration.class)
Expand Down Expand Up @@ -574,6 +583,8 @@ public static class SensibleProperties {

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<List<ListItem>> listOfListItems = new ArrayList<>();
Expand All @@ -599,6 +610,14 @@ public URI getNoPasswordUri() {
return this.noPasswordUri;
}

public String getRawSensitiveAddresses() {
return this.rawSensitiveAddresses;
}

public void setRawSensitiveAddresses(final String rawSensitiveAddresses) {
this.rawSensitiveAddresses = rawSensitiveAddresses;
}

public List<ListItem> getListItems() {
return this.listItems;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
package org.springframework.boot.actuate.endpoint;

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;

Expand All @@ -25,11 +29,12 @@
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Chris Bono
*/
class SanitizerTests {

@Test
void defaults() {
void defaultNonUriKeys() {
Sanitizer sanitizer = new Sanitizer();
assertThat(sanitizer.sanitize("password", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("my-password", "secret")).isEqualTo("******");
Expand All @@ -40,21 +45,64 @@ void defaults() {
assertThat(sanitizer.sanitize("sometoken", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("find", "secret")).isEqualTo("secret");
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
void uriWithNoPasswordShouldNotBeSanitized() {
@ParameterizedTest(name = "key = {0}")
@MethodSource("matchingUriUserInfoKeys")
void uriWithSingleEntryWithPasswordShouldBeSanitized(String key) {
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
void uriWithPasswordMatchingOtherPartsOfString() {
@ParameterizedTest(name = "key = {0}")
@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();
assertThat(sanitizer.sanitize("my.uri", "http://user://@localhost:8080"))
.isEqualTo("http://user:******@localhost:8080");
assertThat(sanitizer.sanitize(key, "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
Expand All @@ -63,5 +111,4 @@ void regex() {
assertThat(sanitizer.sanitize("verylOCkish", "secret")).isEqualTo("******");
assertThat(sanitizer.sanitize("veryokish", "secret")).isEqualTo("secret");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
* @author Madhura Bhave
* @author Andy Wilkinson
* @author HaiTao Zhang
* @author Chris Bono
*/
class EnvironmentEndpointTests {

Expand Down Expand Up @@ -246,13 +247,21 @@ void multipleSourcesWithSameProperty() {
}

@Test
void uriPropertryWithSensitiveInfo() {
void uriPropertyWithSensitiveInfo() {
ConfigurableEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("sensitive.uri=http://user:password@localhost:8080").applyTo(environment);
EnvironmentEntryDescriptor descriptor = new EnvironmentEndpoint(environment).environmentEntry("sensitive.uri");
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() {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
Expand Down