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