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:
Phillip Webb 2014-07-29 15:13:45 -07:00
parent 4f440fcbb9
commit 642224feff
7 changed files with 293 additions and 25 deletions

View File

@ -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.

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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");
}
}

View File

@ -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