Commit 7da70a52 authored by Madhura Bhave's avatar Madhura Bhave

Mask sensitive placeholders in env endpoint

Closes gh-8282
parent 703b7d92
...@@ -26,7 +26,10 @@ import org.springframework.core.env.ConfigurableEnvironment; ...@@ -26,7 +26,10 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
/** /**
...@@ -35,6 +38,7 @@ import org.springframework.core.env.StandardEnvironment; ...@@ -35,6 +38,7 @@ import org.springframework.core.env.StandardEnvironment;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
* @author Madhura Bhave
*/ */
@ConfigurationProperties(prefix = "endpoints.env") @ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> { public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
...@@ -56,14 +60,15 @@ 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() { public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>(); Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put("profiles", getEnvironment().getActiveProfiles()); 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(); PropertySource<?> source = entry.getValue();
String sourceName = entry.getKey(); String sourceName = entry.getKey();
if (source instanceof EnumerablePropertySource) { if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source; EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> properties = new LinkedHashMap<String, Object>(); Map<String, Object> properties = new LinkedHashMap<String, Object>();
for (String name : enumerable.getPropertyNames()) { 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); properties = postProcessSourceProperties(sourceName, properties);
if (properties != null) { if (properties != null) {
...@@ -74,9 +79,24 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> { ...@@ -74,9 +79,24 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
return result; 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<?>>(); 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(); Environment environment = getEnvironment();
if (environment != null && environment instanceof ConfigurableEnvironment) { if (environment != null && environment instanceof ConfigurableEnvironment) {
sources = ((ConfigurableEnvironment) environment).getPropertySources(); sources = ((ConfigurableEnvironment) environment).getPropertySources();
...@@ -84,10 +104,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> { ...@@ -84,10 +104,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
else { else {
sources = new StandardEnvironment().getPropertySources(); sources = new StandardEnvironment().getPropertySources();
} }
for (PropertySource<?> source : sources) { return sources;
extract("", map, source);
}
return map;
} }
private void extract(String root, Map<String, PropertySource<?>> map, private void extract(String root, Map<String, PropertySource<?>> map,
...@@ -120,4 +137,32 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> { ...@@ -120,4 +137,32 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
return properties; 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; ...@@ -22,10 +22,8 @@ import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
...@@ -95,14 +93,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter ...@@ -95,14 +93,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter
@Override @Override
protected Object getOptionalValue(Environment source, String name) { protected Object getOptionalValue(Environment source, String name) {
PropertyResolver resolver = source; Object result = ((EnvironmentEndpoint) getDelegate()).getResolver().getProperty(name);
if (source instanceof ConfigurableEnvironment) {
resolver = new PropertySourcesPropertyResolver(
((ConfigurableEnvironment) source).getPropertySources());
((PropertySourcesPropertyResolver) resolver)
.setIgnoreUnresolvableNestedPlaceholders(true);
}
Object result = resolver.getProperty(name);
if (result != null) { if (result != null) {
result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result); result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
} }
......
...@@ -39,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -39,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Dupuis * @author Christian Dupuis
* @author Nicolas Lejeune * @author Nicolas Lejeune
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Madhura Bhave
*/ */
public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> { public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> {
...@@ -194,6 +195,66 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE ...@@ -194,6 +195,66 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentE
assertThat(systemProperties.get("apiKey")).isEqualTo("******"); 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 @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
public static class Config { public static class Config {
......
...@@ -148,6 +148,17 @@ public class EnvironmentMvcEndpointTests { ...@@ -148,6 +148,17 @@ public class EnvironmentMvcEndpointTests {
.andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\""))); .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 @Configuration
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment