Support regex in keys-to-sanitize
Update EnvironmentEndpoint and ConfigurationPropertiesReportEndpoint to allow regex patterns in `keys-to-sanitize`. Fixes gh-1245
This commit is contained in:
@@ -29,7 +29,6 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
@@ -66,7 +65,7 @@ public class ConfigurationPropertiesReportEndpoint extends
|
||||
|
||||
private static final String CGLIB_FILTER_ID = "cglibFilter";
|
||||
|
||||
private String[] keysToSanitize = new String[] { "password", "secret", "key" };
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
@@ -87,8 +86,7 @@ public class ConfigurationPropertiesReportEndpoint extends
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
this.keysToSanitize = keysToSanitize;
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,21 +190,12 @@ public class ConfigurationPropertiesReportEndpoint extends
|
||||
map.put(entry.getKey(), sanitize((Map<String, Object>) entry.getValue()));
|
||||
}
|
||||
else {
|
||||
map.put(entry.getKey(), sanitize(entry.getKey(), entry.getValue()));
|
||||
map.put(entry.getKey(), this.sanitizer.sanitize(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Object sanitize(String name, Object object) {
|
||||
for (String keyToSanitize : this.keysToSanitize) {
|
||||
if (name.toLowerCase().endsWith(keyToSanitize)) {
|
||||
return (object == null ? null : "******");
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension to {@link JacksonAnnotationIntrospector} to suppress CGLIB generated bean
|
||||
* properties.
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
@@ -48,7 +47,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private String[] keysToSanitize = new String[] { "password", "secret", "key" };
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
|
||||
/**
|
||||
* Create a new {@link EnvironmentEndpoint} instance.
|
||||
@@ -58,8 +57,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
this.keysToSanitize = keysToSanitize;
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,12 +122,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
|
||||
}
|
||||
|
||||
public Object sanitize(String name, Object object) {
|
||||
for (String keyToSanitize : this.keysToSanitize) {
|
||||
if (name.toLowerCase().endsWith(keyToSanitize)) {
|
||||
return (object == null ? null : "******");
|
||||
}
|
||||
}
|
||||
return object;
|
||||
return this.sanitizer.sanitize(name, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Internal strategy used to sanitize potentially sensitive keys.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class Sanitizer {
|
||||
|
||||
private static final String[] REGEX_PARTS = { "*", "$", "^", "+" };
|
||||
|
||||
private Pattern[] keysToSanitize;
|
||||
|
||||
public Sanitizer() {
|
||||
setKeysToSanitize(new String[] { "password", "secret", "key" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys that should be sanitize. Keys can be simple strings that the property
|
||||
* ends with or regex expressions.
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
*/
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
this.keysToSanitize = new Pattern[keysToSanitize.length];
|
||||
for (int i = 0; i < keysToSanitize.length; i++) {
|
||||
this.keysToSanitize[i] = getPattern(keysToSanitize[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private 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) {
|
||||
for (String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
for (Pattern pattern : this.keysToSanitize) {
|
||||
if (pattern.matcher(key).matches()) {
|
||||
return (value == null ? null : "******");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -79,6 +81,71 @@ public class ConfigurationPropertiesReportEndpointTests extends
|
||||
assertEquals("******", nestedProperties.get("myTestProperty"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPattern() throws Exception {
|
||||
ConfigurationPropertiesReportEndpoint report = getEndpointBean();
|
||||
report.setKeysToSanitize(".*pass.*");
|
||||
Map<String, Object> properties = report.invoke();
|
||||
Map<String, Object> nestedProperties = (Map<String, Object>) ((Map<String, Object>) properties
|
||||
.get("testProperties")).get("properties");
|
||||
assertNotNull(nestedProperties);
|
||||
assertEquals("******", nestedProperties.get("dbPassword"));
|
||||
assertEquals("654321", nestedProperties.get("myTestProperty"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomKeysByEnvironment() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.configprops.keys-to-sanitize:property");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
ConfigurationPropertiesReportEndpoint report = getEndpointBean();
|
||||
Map<String, Object> properties = report.invoke();
|
||||
Map<String, Object> nestedProperties = (Map<String, Object>) ((Map<String, Object>) properties
|
||||
.get("testProperties")).get("properties");
|
||||
assertNotNull(nestedProperties);
|
||||
assertEquals("123456", nestedProperties.get("dbPassword"));
|
||||
assertEquals("******", nestedProperties.get("myTestProperty"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPatternByEnvironment() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.configprops.keys-to-sanitize: .*pass.*");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
ConfigurationPropertiesReportEndpoint report = getEndpointBean();
|
||||
Map<String, Object> properties = report.invoke();
|
||||
Map<String, Object> nestedProperties = (Map<String, Object>) ((Map<String, Object>) properties
|
||||
.get("testProperties")).get("properties");
|
||||
assertNotNull(nestedProperties);
|
||||
assertEquals("******", nestedProperties.get("dbPassword"));
|
||||
assertEquals("654321", nestedProperties.get("myTestProperty"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPatternAndKeyByEnvironment()
|
||||
throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.configprops.keys-to-sanitize: .*pass.*, property");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
ConfigurationPropertiesReportEndpoint report = getEndpointBean();
|
||||
Map<String, Object> properties = report.invoke();
|
||||
Map<String, Object> nestedProperties = (Map<String, Object>) ((Map<String, Object>) properties
|
||||
.get("testProperties")).get("properties");
|
||||
assertNotNull(nestedProperties);
|
||||
assertEquals("******", nestedProperties.get("dbPassword"));
|
||||
assertEquals("******", nestedProperties.get("myTestProperty"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class Parent {
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
@@ -74,6 +76,89 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomKeys() throws Exception {
|
||||
System.setProperty("dbPassword", "123456");
|
||||
System.setProperty("apiKey", "123456");
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
report.setKeysToSanitize("key");
|
||||
Map<String, Object> env = report.invoke();
|
||||
assertEquals("123456",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("dbPassword"));
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPattern() throws Exception {
|
||||
System.setProperty("dbPassword", "123456");
|
||||
System.setProperty("apiKey", "123456");
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
report.setKeysToSanitize(".*pass.*");
|
||||
Map<String, Object> env = report.invoke();
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("dbPassword"));
|
||||
assertEquals("123456",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomKeysByEnvironment() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.env.keys-to-sanitize: key");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
System.setProperty("dbPassword", "123456");
|
||||
System.setProperty("apiKey", "123456");
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
assertEquals("123456",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("dbPassword"));
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPatternByEnvironment() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.env.keys-to-sanitize: .*pass.*");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
System.setProperty("dbPassword", "123456");
|
||||
System.setProperty("apiKey", "123456");
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("dbPassword"));
|
||||
assertEquals("123456",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testKeySanitizationWithCustomPatternAndKeyByEnvironment()
|
||||
throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"endpoints.env.keys-to-sanitize: .*pass.*, key");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
System.setProperty("dbPassword", "123456");
|
||||
System.setProperty("apiKey", "123456");
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("dbPassword"));
|
||||
assertEquals("******",
|
||||
((Map<String, Object>) env.get("systemProperties")).get("apiKey"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class Config {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link Sanitizer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SanitizerTests {
|
||||
|
||||
private Sanitizer sanitizer = new Sanitizer();
|
||||
|
||||
@Test
|
||||
public void defaults() throws Exception {
|
||||
assertEquals(this.sanitizer.sanitize("password", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("my-password", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("my-OTHER.paSSword", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("somesecret", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("somekey", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("find", "secret"), "secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regex() throws Exception {
|
||||
this.sanitizer.setKeysToSanitize(".*lock.*");
|
||||
assertEquals(this.sanitizer.sanitize("verylOCkish", "secret"), "******");
|
||||
assertEquals(this.sanitizer.sanitize("veryokish", "secret"), "secret");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -337,13 +337,14 @@ content into your application; rather pick only the properties that you need.
|
||||
endpoints.configprops.id=configprops
|
||||
endpoints.configprops.sensitive=true
|
||||
endpoints.configprops.enabled=true
|
||||
endpoints.configprops.keys-to-sanitize=password,secret
|
||||
endpoints.configprops.keys-to-sanitize=password,secret,key # suffix or regex
|
||||
endpoints.dump.id=dump
|
||||
endpoints.dump.sensitive=true
|
||||
endpoints.dump.enabled=true
|
||||
endpoints.env.id=env
|
||||
endpoints.env.sensitive=true
|
||||
endpoints.env.enabled=true
|
||||
endpoints.env.keys-to-sanitize=password,secret,key # suffix or regex
|
||||
endpoints.health.id=health
|
||||
endpoints.health.sensitive=false
|
||||
endpoints.health.enabled=true
|
||||
|
||||
Reference in New Issue
Block a user