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 e620eba1d78..8c029bfb6b7 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 @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.context.properties; +import java.util.stream.Collectors; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; @@ -48,7 +50,8 @@ public class ConfigurationPropertiesReportEndpointAutoConfiguration { public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint( ConfigurationPropertiesReportEndpointProperties properties, ObjectProvider sanitizingFunctions) { - ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(sanitizingFunctions); + ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint( + sanitizingFunctions.orderedStream().collect(Collectors.toList())); String[] keysToSanitize = properties.getKeysToSanitize(); if (keysToSanitize != null) { endpoint.setKeysToSanitize(keysToSanitize); 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 570b81e5942..e6af2bc2f6a 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,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.env; +import java.util.stream.Collectors; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; @@ -46,7 +48,8 @@ public class EnvironmentEndpointAutoConfiguration { @ConditionalOnMissingBean public EnvironmentEndpoint environmentEndpoint(Environment environment, EnvironmentEndpointProperties properties, ObjectProvider sanitizingFunctions) { - EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment, sanitizingFunctions); + EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment, + sanitizingFunctions.orderedStream().collect(Collectors.toList())); String[] keysToSanitize = properties.getKeysToSanitize(); if (keysToSanitize != null) { endpoint.setKeysToSanitize(keysToSanitize); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java index 825950d5114..035abffdea2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -32,6 +32,7 @@ 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 org.springframework.core.annotation.Order; import static org.assertj.core.api.Assertions.assertThat; @@ -75,11 +76,11 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests { } @Test - void customSanitizingFunctionShouldBeApplied() { + void customSanitizingFunctionsAreAppliedInOrder() { this.contextRunner.withUserConfiguration(Config.class, SanitizingFunctionConfiguration.class) .withPropertyValues("management.endpoints.web.exposure.include=configprops", "test.my-test-property=abc") - .run(validateTestProperties("******", "$$$")); + .run(validateTestProperties("$$$111$$$", "$$$222$$$")); } @Test @@ -152,10 +153,22 @@ class ConfigurationPropertiesReportEndpointAutoConfigurationTests { static class SanitizingFunctionConfiguration { @Bean - SanitizingFunction testSanitizingFunction() { + @Order(0) + SanitizingFunction firstSanitizingFunction() { return (data) -> { - if (data.getKey().contains("my")) { - return data.withValue("$$$"); + if (data.getKey().contains("Password")) { + return data.withValue("$$$111$$$"); + } + return data; + }; + } + + @Bean + @Order(1) + SanitizingFunction secondSanitizingFunction() { + return (data) -> { + if (data.getKey().contains("Password") || data.getKey().contains("test")) { + return data.withValue("$$$222$$$"); } return data; }; 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 467e77f5f1f..1f2b93d0134 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -32,6 +32,7 @@ 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 org.springframework.core.annotation.Order; import static org.assertj.core.api.Assertions.assertThat; @@ -72,7 +73,7 @@ class EnvironmentEndpointAutoConfigurationTests { } @Test - void sanitizingFunctionsCanBeConfiguredViaTheEnvironment() { + void customSanitizingFunctionsAreAppliedInOrder() { this.contextRunner.withUserConfiguration(SanitizingFunctionConfiguration.class) .withPropertyValues("management.endpoints.web.exposure.include=env") .withSystemProperties("custom=123456", "password=123456").run((context) -> { @@ -81,8 +82,8 @@ class EnvironmentEndpointAutoConfigurationTests { EnvironmentDescriptor env = endpoint.environment(null); Map systemProperties = getSource("systemProperties", env) .getProperties(); - assertThat(systemProperties.get("custom").getValue()).isEqualTo("$$$"); - assertThat(systemProperties.get("password").getValue()).isEqualTo("******"); + assertThat(systemProperties.get("custom").getValue()).isEqualTo("$$$111$$$"); + assertThat(systemProperties.get("password").getValue()).isEqualTo("$$$222$$$"); }); } @@ -123,8 +124,25 @@ class EnvironmentEndpointAutoConfigurationTests { static class SanitizingFunctionConfiguration { @Bean - SanitizingFunction testSanitizingFunction() { - return (data) -> data.withValue("$$$"); + @Order(0) + SanitizingFunction firstSanitizingFunction() { + return (data) -> { + if (data.getKey().contains("custom")) { + return data.withValue("$$$111$$$"); + } + return data; + }; + } + + @Bean + @Order(1) + SanitizingFunction secondSanitizingFunction() { + return (data) -> { + if (data.getKey().contains("custom") || data.getKey().contains("password")) { + return data.withValue("$$$222$$$"); + } + return data; + }; } } 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/Sanitizer.java index cc5c2b941b3..02e3ac96bcf 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/Sanitizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -184,13 +184,18 @@ public class Sanitizer { * @since 2.6.0 */ public Object sanitize(SanitizableData data) { - if (data.getValue() == null) { + Object value = data.getValue(); + if (value == null) { return null; } for (SanitizingFunction sanitizingFunction : this.sanitizingFunctions) { data = sanitizingFunction.apply(data); + Object sanitizedValue = data.getValue(); + if (!value.equals(sanitizedValue)) { + return sanitizedValue; + } } - return data.getValue(); + return value; } private boolean keyIsUriWithUserInfo(Pattern pattern) { 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 b98c72b311f..cc424053b4b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -293,7 +293,7 @@ class ConfigurationPropertiesReportEndpointTests { new ApplicationContextRunner().withUserConfiguration(CustomSanitizingEndpointConfig.class, SanitizingFunctionConfiguration.class, TestPropertiesConfiguration.class) .run(assertProperties("test", (properties) -> { - assertThat(properties.get("dbPassword")).isEqualTo("******"); + assertThat(properties.get("dbPassword")).isEqualTo("$$$"); assertThat(properties.get("myTestProperty")).isEqualTo("$$$"); })); } 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 7d30f24c0b1..a29771f0585 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -16,7 +16,9 @@ package org.springframework.boot.actuate.endpoint; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -87,6 +89,39 @@ class SanitizerTests { assertThat(sanitizer.sanitize(hello)).isEqualTo("abc"); } + @Test + void overridingDefaultSanitizingFunction() { + Sanitizer sanitizer = new Sanitizer(Collections.singletonList((data) -> { + if (data.getKey().equals("password")) { + return data.withValue("------"); + } + return data; + })); + SanitizableData password = new SanitizableData(null, "password", "123456"); + assertThat(sanitizer.sanitize(password)).isEqualTo("------"); + } + + @Test + void whenValueSanitizedLaterSanitizingFunctionsShouldBeSkipped() { + final String sameKey = "custom"; + List sanitizingFunctions = new ArrayList<>(); + sanitizingFunctions.add((data) -> { + if (data.getKey().equals(sameKey)) { + return data.withValue("------"); + } + return data; + }); + sanitizingFunctions.add((data) -> { + if (data.getKey().equals(sameKey)) { + return data.withValue("******"); + } + return data; + }); + Sanitizer sanitizer = new Sanitizer(sanitizingFunctions); + SanitizableData custom = new SanitizableData(null, sameKey, "123456"); + assertThat(sanitizer.sanitize(custom)).isEqualTo("------"); + } + @ParameterizedTest(name = "key = {0}") @MethodSource("matchingUriUserInfoKeys") void uriWithSingleValueWithPasswordShouldBeSanitized(String key) { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc index fa877888384..4ea1ab0e46b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/actuator.adoc @@ -65,7 +65,8 @@ Alternatively, additional patterns can be configured using configprop:management To take more control over the santization, define a `SanitizingFunction` bean. The `SanitizableData` with which the function is called provides access to the key and value as well as the `PropertySource` from which they came. This allows you to, for example, sanitize every value that comes from a particular property source. -Each `SanitizingFunction` is called before and in addition to the built-in key-based sanitization. +Each `SanitizingFunction` is called in order until a function changes the value of the santizable data. +If no function changes its value, the built-in key-based santization is performed.