Mask sensitive placeholders in env endpoint
Closes gh-8282
This commit is contained in:
parent
703b7d9268
commit
7da70a52fd
|
|
@ -26,7 +26,10 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
|||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.core.env.PropertySourcesPropertyResolver;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
/**
|
||||
|
|
@ -35,6 +38,7 @@ import org.springframework.core.env.StandardEnvironment;
|
|||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Christian Dupuis
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.env")
|
||||
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
||||
|
|
@ -56,14 +60,15 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
|||
public Map<String, Object> invoke() {
|
||||
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||
result.put("profiles", getEnvironment().getActiveProfiles());
|
||||
for (Entry<String, PropertySource<?>> entry : getPropertySources().entrySet()) {
|
||||
PropertyResolver resolver = getResolver();
|
||||
for (Entry<String, PropertySource<?>> entry : getPropertySourcesAsMap().entrySet()) {
|
||||
PropertySource<?> source = entry.getValue();
|
||||
String sourceName = entry.getKey();
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
|
||||
Map<String, Object> properties = new LinkedHashMap<String, Object>();
|
||||
for (String name : enumerable.getPropertyNames()) {
|
||||
properties.put(name, sanitize(name, enumerable.getProperty(name)));
|
||||
properties.put(name, sanitize(name, resolver.getProperty(name)));
|
||||
}
|
||||
properties = postProcessSourceProperties(sourceName, properties);
|
||||
if (properties != null) {
|
||||
|
|
@ -74,9 +79,24 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
|||
return result;
|
||||
}
|
||||
|
||||
private Map<String, PropertySource<?>> getPropertySources() {
|
||||
public PropertyResolver getResolver() {
|
||||
PlaceholderSanitizingPropertyResolver resolver = new PlaceholderSanitizingPropertyResolver(
|
||||
getPropertySources(), this.sanitizer);
|
||||
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
||||
Map<String, PropertySource<?>> map = new LinkedHashMap<String, PropertySource<?>>();
|
||||
MutablePropertySources sources = null;
|
||||
MutablePropertySources sources = getPropertySources();
|
||||
for (PropertySource<?> source : sources) {
|
||||
extract("", map, source);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private MutablePropertySources getPropertySources() {
|
||||
MutablePropertySources sources;
|
||||
Environment environment = getEnvironment();
|
||||
if (environment != null && environment instanceof ConfigurableEnvironment) {
|
||||
sources = ((ConfigurableEnvironment) environment).getPropertySources();
|
||||
|
|
@ -84,10 +104,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
|||
else {
|
||||
sources = new StandardEnvironment().getPropertySources();
|
||||
}
|
||||
for (PropertySource<?> source : sources) {
|
||||
extract("", map, source);
|
||||
}
|
||||
return map;
|
||||
return sources;
|
||||
}
|
||||
|
||||
private void extract(String root, Map<String, PropertySource<?>> map,
|
||||
|
|
@ -120,4 +137,32 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
|||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertySourcesPropertyResolver} that sanitizes sensitive placeholders
|
||||
* if present.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
private class PlaceholderSanitizingPropertyResolver extends PropertySourcesPropertyResolver {
|
||||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
/**
|
||||
* Create a new resolver against the given property sources.
|
||||
* @param propertySources the set of {@link PropertySource} objects to use
|
||||
* @param sanitizer the sanitizer used to sanitize sensitive values
|
||||
*/
|
||||
PlaceholderSanitizingPropertyResolver(PropertySources
|
||||
propertySources, Sanitizer sanitizer) {
|
||||
super(propertySources);
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPropertyAsRawString(String key) {
|
||||
String value = super.getPropertyAsRawString(key);
|
||||
return (String) this.sanitizer.sanitize(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ import org.springframework.context.EnvironmentAware;
|
|||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.core.env.PropertySourcesPropertyResolver;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
|
@ -95,14 +93,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter
|
|||
|
||||
@Override
|
||||
protected Object getOptionalValue(Environment source, String name) {
|
||||
PropertyResolver resolver = source;
|
||||
if (source instanceof ConfigurableEnvironment) {
|
||||
resolver = new PropertySourcesPropertyResolver(
|
||||
((ConfigurableEnvironment) source).getPropertySources());
|
||||
((PropertySourcesPropertyResolver) resolver)
|
||||
.setIgnoreUnresolvableNestedPlaceholders(true);
|
||||
}
|
||||
Object result = resolver.getProperty(name);
|
||||
Object result = ((EnvironmentEndpoint) getDelegate()).getResolver().getProperty(name);
|
||||
if (result != null) {
|
||||
result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* @author Christian Dupuis
|
||||
* @author Nicolas Lejeune
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> {
|
||||
|
||||
|
|
@ -194,6 +195,66 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
|
|||
assertThat(systemProperties.get("apiKey")).isEqualTo("******");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void propertyWithPlaceholderResolved() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"my.foo: ${bar.blah}", "bar.blah: hello");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
Map<String, Object> testProperties = (Map<String, Object>) env
|
||||
.get("test");
|
||||
assertThat(testProperties.get("my.foo")).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void propertyWithPlaceholderNotResolved() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"my.foo: ${bar.blah}");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
Map<String, Object> testProperties = (Map<String, Object>) env
|
||||
.get("test");
|
||||
assertThat(testProperties.get("my.foo")).isEqualTo("${bar.blah}");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void propertyWithSensitivePlaceholderResolved() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"my.foo: http://${bar.password}://hello", "bar.password: hello");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
Map<String, Object> testProperties = (Map<String, Object>) env
|
||||
.get("test");
|
||||
assertThat(testProperties.get("my.foo")).isEqualTo("http://******://hello");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void propertyWithSensitivePlaceholderNotResolved() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"my.foo: http://${bar.password}://hello");
|
||||
this.context.register(Config.class);
|
||||
this.context.refresh();
|
||||
EnvironmentEndpoint report = getEndpointBean();
|
||||
Map<String, Object> env = report.invoke();
|
||||
Map<String, Object> testProperties = (Map<String, Object>) env
|
||||
.get("test");
|
||||
assertThat(testProperties.get("my.foo")).isEqualTo("http://${bar.password}://hello");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class Config {
|
||||
|
|
|
|||
|
|
@ -148,6 +148,17 @@ public class EnvironmentMvcEndpointTests {
|
|||
.andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedPathWithSensitivePlaceholderShouldSanitize() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("my.foo", "${my.password}");
|
||||
map.put("my.password", "hello");
|
||||
((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources()
|
||||
.addFirst(new MapPropertySource("placeholder", map));
|
||||
this.mvc.perform(get("/env/my.*")).andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"my.foo\":\"******\"")));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
|
|
|
|||
Loading…
Reference in New Issue