Remove concept of sensitivity from Actuator's endpoints

Closes gh-9924
This commit is contained in:
Andy Wilkinson 2017-08-01 10:05:09 +01:00
parent 847f6d1b2c
commit bb55f49396
55 changed files with 173 additions and 606 deletions

View File

@ -26,7 +26,6 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
@ -62,7 +61,6 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity.I
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
@ -77,11 +75,7 @@ import org.springframework.util.StringUtils;
* {@link EnableAutoConfiguration Auto-configuration} for security of framework endpoints.
* Many aspects of the behavior can be controller with {@link ManagementServerProperties}
* via externalized application properties (or via an bean definition of that type to set
* the defaults).
* <p>
* The framework {@link Endpoint}s (used to expose application information to operations)
* include a {@link Endpoint#isSensitive() sensitive} configuration option which will be
* used as a security hint by the filter created here.
* the defaults)..
*
* @author Dave Syer
* @author Andy Wilkinson
@ -126,7 +120,6 @@ public class ManagementWebSecurityAutoConfiguration {
.getRequestMatcher(this.contextResolver);
configurer.requestMatchers(requestMatcher);
}
}
}
@ -223,8 +216,6 @@ public class ManagementWebSecurityAutoConfiguration {
http.exceptionHandling().authenticationEntryPoint(entryPoint);
// Match all the requests for actuator endpoints ...
http.requestMatcher(matcher);
// ... but permitAll() for the non-sensitive ones
configurePermittedRequests(http.authorizeRequests());
http.httpBasic().authenticationEntryPoint(entryPoint).and().cors();
// No cookies for management endpoints by default
http.csrf().disable();
@ -258,38 +249,9 @@ public class ManagementWebSecurityAutoConfiguration {
return entryPoint;
}
private void configurePermittedRequests(
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests) {
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
this.contextResolver, EndpointPaths.SENSITIVE)).authenticated();
// Permit access to the non-sensitive endpoints
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
this.contextResolver, EndpointPaths.NON_SENSITIVE)).permitAll();
}
}
private enum EndpointPaths {
ALL,
NON_SENSITIVE {
@Override
protected boolean isIncluded(MvcEndpoint endpoint) {
return !endpoint.isSensitive();
}
},
SENSITIVE {
@Override
protected boolean isIncluded(MvcEndpoint endpoint) {
return endpoint.isSensitive();
}
};
private static class EndpointPaths {
public String[] getPaths(EndpointHandlerMapping endpointHandlerMapping) {
if (endpointHandlerMapping == null) {
@ -298,24 +260,18 @@ public class ManagementWebSecurityAutoConfiguration {
Set<? extends MvcEndpoint> endpoints = endpointHandlerMapping.getEndpoints();
Set<String> paths = new LinkedHashSet<>(endpoints.size());
for (MvcEndpoint endpoint : endpoints) {
if (isIncluded(endpoint)) {
String path = endpointHandlerMapping.getPath(endpoint.getPath());
paths.add(path);
if (!path.equals("")) {
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
}
paths.add(path + "/");
String path = endpointHandlerMapping.getPath(endpoint.getPath());
paths.add(path);
if (!path.equals("")) {
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
}
paths.add(path + "/");
}
return paths.toArray(new String[paths.size()]);
}
protected boolean isIncluded(MvcEndpoint endpoint) {
return true;
}
}
private static class LazyEndpointPathRequestMatcher implements RequestMatcher {
@ -342,7 +298,8 @@ public class ManagementWebSecurityAutoConfiguration {
return matcher;
}
// Match everything, including the sensitive and non-sensitive paths
return new LazyEndpointPathRequestMatcher(contextResolver, EndpointPaths.ALL);
return new LazyEndpointPathRequestMatcher(contextResolver,
new EndpointPaths());
}
LazyEndpointPathRequestMatcher(ManagementContextResolver contextResolver,

View File

@ -41,7 +41,7 @@ class CloudFoundryDiscoveryMvcEndpoint extends AbstractMvcEndpoint {
private final Set<NamedMvcEndpoint> endpoints;
CloudFoundryDiscoveryMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
super("", false);
super("");
this.endpoints = endpoints;
}

View File

@ -41,47 +41,27 @@ public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAwa
*/
private String id;
private final boolean sensitiveDefault;
/**
* Mark if the endpoint exposes sensitive information.
*/
private Boolean sensitive;
/**
* Enable the endpoint.
*/
private Boolean enabled;
/**
* Create a new sensitive endpoint instance. The endpoint will enabled flag will be
* based on the spring {@link Environment} unless explicitly set.
* @param id the endpoint ID
*/
public AbstractEndpoint(String id) {
this(id, true);
}
/**
* Create a new endpoint instance. The endpoint will enabled flag will be based on the
* spring {@link Environment} unless explicitly set.
* @param id the endpoint ID
* @param sensitive if the endpoint is sensitive by default
*/
public AbstractEndpoint(String id, boolean sensitive) {
public AbstractEndpoint(String id) {
setId(id);
this.sensitiveDefault = sensitive;
}
/**
* Create a new endpoint instance.
* @param id the endpoint ID
* @param sensitive if the endpoint is sensitive
* @param enabled if the endpoint is enabled or not.
*/
public AbstractEndpoint(String id, boolean sensitive, boolean enabled) {
public AbstractEndpoint(String id, boolean enabled) {
setId(id);
this.sensitiveDefault = sensitive;
this.enabled = enabled;
}
@ -115,14 +95,4 @@ public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAwa
this.enabled = enabled;
}
@Override
public boolean isSensitive() {
return EndpointProperties.isSensitive(this.environment, this.sensitive,
this.sensitiveDefault);
}
public void setSensitive(Boolean sensitive) {
this.sensitive = sensitive;
}
}

View File

@ -42,13 +42,6 @@ public interface Endpoint<T> {
*/
boolean isEnabled();
/**
* Return if the endpoint is sensitive, i.e. may return data that the average user
* should not see. Mappings can use this as a security hint.
* @return if the endpoint is sensitive
*/
boolean isSensitive();
/**
* Called to invoke the endpoint.
* @return the results of the invocation

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2017 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.
@ -30,18 +30,11 @@ public class EndpointProperties {
private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";
private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive";
/**
* Enable endpoints.
*/
private Boolean enabled = true;
/**
* Default endpoint sensitive setting.
*/
private Boolean sensitive;
public Boolean getEnabled() {
return this.enabled;
}
@ -50,14 +43,6 @@ public class EndpointProperties {
this.enabled = enabled;
}
public Boolean getSensitive() {
return this.sensitive;
}
public void setSensitive(Boolean sensitive) {
this.sensitive = sensitive;
}
/**
* Determine if an endpoint is enabled based on its specific property and taking into
* account the global default.
@ -76,25 +61,4 @@ public class EndpointProperties {
return true;
}
/**
* Determine if an endpoint is sensitive based on its specific property and taking
* into account the global default.
* @param environment the Spring environment or {@code null}.
* @param sensitive the endpoint property or {@code null}
* @param sensitiveDefault the default setting to use if no environment property is
* defined
* @return if the endpoint is sensitive
*/
public static boolean isSensitive(Environment environment, Boolean sensitive,
boolean sensitiveDefault) {
if (sensitive != null) {
return sensitive;
}
if (environment != null
&& environment.containsProperty(ENDPOINTS_SENSITIVE_PROPERTY)) {
return environment.getProperty(ENDPOINTS_SENSITIVE_PROPERTY, Boolean.class);
}
return sensitiveDefault;
}
}

View File

@ -49,7 +49,7 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
*/
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
super("health", false);
super("health");
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(

View File

@ -41,7 +41,7 @@ public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
* @param infoContributors the info contributors to use
*/
public InfoEndpoint(List<InfoContributor> infoContributors) {
super("info", false);
super("info");
Assert.notNull(infoContributors, "Info contributors must not be null");
this.infoContributors = infoContributors;
}

View File

@ -50,7 +50,7 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
* Create a new {@link ShutdownEndpoint} instance.
*/
public ShutdownEndpoint() {
super("shutdown", true, false);
super("shutdown", false);
}
@Override

View File

@ -64,11 +64,6 @@ public abstract class EndpointMBean implements JmxEndpoint {
return this.endpoint.isEnabled();
}
@ManagedAttribute(description = "Indicates whether the underlying endpoint exposes sensitive information")
public boolean isSensitive() {
return this.endpoint.isSensitive();
}
@Override
public String getIdentity() {
return ObjectUtils.getIdentityHexString(getEndpoint());

View File

@ -80,11 +80,6 @@ public abstract class AbstractEndpointMvcAdapter<E extends Endpoint<?>>
this.path = path;
}
@Override
public boolean isSensitive() {
return this.delegate.isSensitive();
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {

View File

@ -46,22 +46,8 @@ public abstract class AbstractMvcEndpoint
*/
private Boolean enabled;
/**
* Mark if the endpoint exposes sensitive information.
*/
private Boolean sensitive;
private final boolean sensitiveDefault;
public AbstractMvcEndpoint(String path, boolean sensitive) {
public AbstractMvcEndpoint(String path) {
setPath(path);
this.sensitiveDefault = sensitive;
}
public AbstractMvcEndpoint(String path, boolean sensitive, boolean enabled) {
setPath(path);
this.sensitiveDefault = sensitive;
this.enabled = enabled;
}
@Override
@ -93,16 +79,6 @@ public abstract class AbstractMvcEndpoint
this.enabled = enabled;
}
@Override
public boolean isSensitive() {
return EndpointProperties.isSensitive(this.environment, this.sensitive,
this.sensitiveDefault);
}
public void setSensitive(Boolean sensitive) {
this.sensitive = sensitive;
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {

View File

@ -31,15 +31,8 @@ public abstract class AbstractNamedMvcEndpoint extends AbstractMvcEndpoint
private final String name;
public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive) {
super(path, sensitive);
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}
public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive,
boolean enabled) {
super(path, sensitive, enabled);
public AbstractNamedMvcEndpoint(String name, String path) {
super(path);
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}

View File

@ -42,7 +42,7 @@ public class AuditEventsMvcEndpoint extends AbstractNamedMvcEndpoint {
private final AuditEventRepository auditEventRepository;
public AuditEventsMvcEndpoint(AuditEventRepository auditEventRepository) {
super("auditevents", "/auditevents", true);
super("auditevents", "/auditevents");
Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
this.auditEventRepository = auditEventRepository;
}

View File

@ -91,7 +91,7 @@ public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping<MvcEn
private final Set<NamedMvcEndpoint> endpoints;
private EndpointLinksMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
super("", false);
super("");
this.endpoints = endpoints;
}

View File

@ -68,7 +68,7 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint {
}
protected HeapdumpMvcEndpoint(long timeout) {
super("heapdump", "/heapdump", true);
super("heapdump", "/heapdump");
this.timeout = timeout;
}

View File

@ -50,7 +50,7 @@ public class JolokiaMvcEndpoint extends AbstractNamedMvcEndpoint implements
private final ServletWrappingController controller = new ServletWrappingController();
public JolokiaMvcEndpoint() {
super("jolokia", "/jolokia", true);
super("jolokia", "/jolokia");
this.controller.setServletClass(AgentServlet.class);
this.controller.setServletName("jolokia");
}

View File

@ -58,7 +58,7 @@ public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint {
private File externalFile;
public LogFileMvcEndpoint() {
super("logfile", "/logfile", true);
super("logfile", "/logfile");
}
public File getExternalFile() {

View File

@ -48,12 +48,6 @@ public interface MvcEndpoint {
*/
String getPath();
/**
* Return if the endpoint exposes sensitive information.
* @return if the endpoint is sensitive
*/
boolean isSensitive();
/**
* Return the type of {@link Endpoint} exposed, or {@code null} if this
* {@link MvcEndpoint} exposes information that cannot be represented as a traditional

View File

@ -69,10 +69,6 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {
&& !(handlerMethod.getBean() instanceof MvcEndpoint)) {
return true;
}
MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
if (!mvcEndpoint.isSensitive()) {
return true;
}
if (isUserAllowedAccess(request)) {
return true;
}

View File

@ -178,7 +178,7 @@ public class EndpointMBeanExportAutoConfigurationTests {
public static class ManagedEndpoint extends AbstractEndpoint<Boolean> {
public ManagedEndpoint() {
super("managed", true);
super("managed");
}
@Override
@ -200,7 +200,7 @@ public class EndpointMBeanExportAutoConfigurationTests {
class Nested extends AbstractEndpoint<Boolean> {
Nested() {
super("managed", true);
super("managed");
}
@Override

View File

@ -867,11 +867,6 @@ public class EndpointWebMvcAutoConfigurationTests {
return "/endpoint";
}
@Override
public boolean isSensitive() {
return true;
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {

View File

@ -64,7 +64,7 @@ public class LinksEnhancerTests {
@Test
public void usePathAsRelIfNameNotAvailable() throws Exception {
MvcEndpoint endpoint = new NoNameTestMvcEndpoint("/a", false);
MvcEndpoint endpoint = new NoNameTestMvcEndpoint("/a");
LinksEnhancer enhancer = getLinksEnhancer(Collections.singletonList(endpoint));
ResourceSupport support = new ResourceSupport();
enhancer.addEndpointLinks(support, "");
@ -143,8 +143,8 @@ public class LinksEnhancerTests {
private static class NoNameTestMvcEndpoint extends AbstractMvcEndpoint {
NoNameTestMvcEndpoint(String path, boolean sensitive) {
super(path, sensitive);
NoNameTestMvcEndpoint(String path) {
super(path);
}
}

View File

@ -57,9 +57,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link ManagementWebSecurityAutoConfiguration}.
@ -226,27 +223,6 @@ public class ManagementWebSecurityAutoConfigurationTests {
.andExpect(springAuthenticateRealmHeader());
}
@Test
public void testMarkAllEndpointsSensitive() throws Exception {
// gh-4368
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(WebConfiguration.class);
TestPropertyValues.of("endpoints.sensitive:true").applyTo(this.context);
this.context.refresh();
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) //
.apply(springSecurity()) //
.build();
mockMvc //
.perform(get("/health")) //
.andExpect(status().isUnauthorized());
mockMvc //
.perform(get("/info")) //
.andExpect(status().isUnauthorized());
}
private ResultMatcher springAuthenticateRealmHeader() {
return MockMvcResultMatchers.header().string("www-authenticate",
Matchers.containsString("realm=\"Spring\""));

View File

@ -41,7 +41,6 @@ public class CloudFoundryHealthMvcEndpointTests {
CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint);
given(endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
given(endpoint.isSensitive()).willReturn(false);
Object result = mvc.invoke(null, null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();

View File

@ -49,16 +49,13 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
private final String id;
private final boolean sensitive;
private final String property;
public AbstractEndpointTests(Class<?> configClass, Class<?> type, String id,
boolean sensitive, String property) {
String property) {
this.configClass = configClass;
this.type = type;
this.id = id;
this.sensitive = sensitive;
this.property = property;
}
@ -81,11 +78,6 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
assertThat(getEndpointBean().getId()).isEqualTo(this.id);
}
@Test
public void isSensitive() throws Exception {
assertThat(getEndpointBean().isSensitive()).isEqualTo(this.sensitive);
}
@Test
public void idOverride() throws Exception {
this.context = new AnnotationConfigApplicationContext();
@ -95,31 +87,6 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
assertThat(getEndpointBean().getId()).isEqualTo("myid");
}
@Test
public void isSensitiveOverride() throws Exception {
this.context = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.<String, Object>singletonMap(this.property + ".sensitive",
String.valueOf(!this.sensitive)));
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().isSensitive()).isEqualTo(!this.sensitive);
}
@Test
public void isSensitiveOverrideWithGlobal() throws Exception {
this.context = new AnnotationConfigApplicationContext();
Map<String, Object> properties = new HashMap<>();
properties.put("endpoint.sensitive", this.sensitive);
properties.put(this.property + ".sensitive", String.valueOf(!this.sensitive));
PropertySource<?> propertySource = new MapPropertySource("test", properties);
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().isSensitive()).isEqualTo(!this.sensitive);
}
@Test
public void isEnabledByDefault() throws Exception {
assertThat(getEndpointBean().isEnabled()).isTrue();
@ -181,26 +148,6 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
}
}
@Test
public void isAllEndpointsSensitive() throws Exception {
testGlobalEndpointsSensitive(true);
}
@Test
public void isAllEndpointsNotSensitive() throws Exception {
testGlobalEndpointsSensitive(false);
}
private void testGlobalEndpointsSensitive(boolean sensitive) {
this.context = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test", Collections
.<String, Object>singletonMap("endpoints.sensitive", sensitive));
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().isSensitive()).isEqualTo(sensitive);
}
@SuppressWarnings("unchecked")
protected T getEndpointBean() {
return (T) this.context.getBean(this.type);

View File

@ -47,7 +47,7 @@ public class AutoConfigurationReportEndpointTests
extends AbstractEndpointTests<AutoConfigurationReportEndpoint> {
public AutoConfigurationReportEndpointTests() {
super(Config.class, AutoConfigurationReportEndpoint.class, "autoconfig", true,
super(Config.class, AutoConfigurationReportEndpoint.class, "autoconfig",
"endpoints.autoconfig");
}

View File

@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeansEndpointTests extends AbstractEndpointTests<BeansEndpoint> {
public BeansEndpointTests() {
super(Config.class, BeansEndpoint.class, "beans", true, "endpoints.beans");
super(Config.class, BeansEndpoint.class, "beans", "endpoints.beans");
}
@Test

View File

@ -43,7 +43,7 @@ public class ConfigurationPropertiesReportEndpointTests
public ConfigurationPropertiesReportEndpointTests() {
super(Config.class, ConfigurationPropertiesReportEndpoint.class, "configprops",
true, "endpoints.configprops");
"endpoints.configprops");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class DumpEndpointTests extends AbstractEndpointTests<DumpEndpoint> {
public DumpEndpointTests() {
super(Config.class, DumpEndpoint.class, "dump", true, "endpoints.dump");
super(Config.class, DumpEndpoint.class, "dump", "endpoints.dump");
}
@Test

View File

@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> {
public EnvironmentEndpointTests() {
super(Config.class, EnvironmentEndpoint.class, "env", true, "endpoints.env");
super(Config.class, EnvironmentEndpoint.class, "env", "endpoints.env");
}
@Override

View File

@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class FlywayEndpointTests extends AbstractEndpointTests<FlywayEndpoint> {
public FlywayEndpointTests() {
super(Config.class, FlywayEndpoint.class, "flyway", true, "endpoints.flyway");
super(Config.class, FlywayEndpoint.class, "flyway", "endpoints.flyway");
}
@Test

View File

@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> {
public HealthEndpointTests() {
super(Config.class, HealthEndpoint.class, "health", false, "endpoints.health");
super(Config.class, HealthEndpoint.class, "health", "endpoints.health");
}
@Test

View File

@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class InfoEndpointTests extends AbstractEndpointTests<InfoEndpoint> {
public InfoEndpointTests() {
super(Config.class, InfoEndpoint.class, "info", false, "endpoints.info");
super(Config.class, InfoEndpoint.class, "info", "endpoints.info");
}
@Test

View File

@ -38,8 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class LiquibaseEndpointTests extends AbstractEndpointTests<LiquibaseEndpoint> {
public LiquibaseEndpointTests() {
super(Config.class, LiquibaseEndpoint.class, "liquibase", true,
"endpoints.liquibase");
super(Config.class, LiquibaseEndpoint.class, "liquibase", "endpoints.liquibase");
}
@Test

View File

@ -44,7 +44,7 @@ import static org.mockito.Mockito.verify;
public class LoggersEndpointTests extends AbstractEndpointTests<LoggersEndpoint> {
public LoggersEndpointTests() {
super(Config.class, LoggersEndpoint.class, "loggers", true, "endpoints.loggers");
super(Config.class, LoggersEndpoint.class, "loggers", "endpoints.loggers");
}
@Test

View File

@ -49,7 +49,7 @@ public class MetricsEndpointTests extends AbstractEndpointTests<MetricsEndpoint>
private Metric<Number> metric3 = new Metric<>("c", 3);
public MetricsEndpointTests() {
super(Config.class, MetricsEndpoint.class, "metrics", true, "endpoints.metrics");
super(Config.class, MetricsEndpoint.class, "metrics", "endpoints.metrics");
}
@Test

View File

@ -42,8 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoint> {
public ShutdownEndpointTests() {
super(Config.class, ShutdownEndpoint.class, "shutdown", true,
"endpoints.shutdown");
super(Config.class, ShutdownEndpoint.class, "shutdown", "endpoints.shutdown");
}
@Override

View File

@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TraceEndpointTests extends AbstractEndpointTests<TraceEndpoint> {
public TraceEndpointTests() {
super(Config.class, TraceEndpoint.class, "trace", true, "endpoints.trace");
super(Config.class, TraceEndpoint.class, "trace", "endpoints.trace");
}
@Test

View File

@ -86,8 +86,8 @@ public class EndpointMBeanExporterTests {
MBeanInfo mbeanInfo = mbeanExporter.getServer()
.getMBeanInfo(getObjectName("endpoint1", this.context));
assertThat(mbeanInfo).isNotNull();
assertThat(mbeanInfo.getOperations().length).isEqualTo(3);
assertThat(mbeanInfo.getAttributes().length).isEqualTo(3);
assertThat(mbeanInfo.getOperations().length).isEqualTo(2);
assertThat(mbeanInfo.getAttributes().length).isEqualTo(2);
}
@Test

View File

@ -58,7 +58,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = { "info.app.name=MyService" })
@TestPropertySource(properties = { "info.app.name=MyService",
"management.security.enabled=false" })
public class InfoMvcEndpointTests {
@Autowired

View File

@ -34,6 +34,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@ -50,6 +51,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = "management.security.enabled=false")
public class InfoMvcEndpointWithNoInfoContributorsTests {
@Autowired

View File

@ -112,28 +112,7 @@ public class MvcEndpointIntegrationTests {
}
@Test
public void nonSensitiveEndpointsAreNotSecureByDefault() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class);
MockMvc mockMvc = createSecureMockMvc();
mockMvc.perform(get("/application/info")).andExpect(status().isOk());
mockMvc.perform(get("/application")).andExpect(status().isOk());
}
@Test
public void nonSensitiveEndpointsAreNotSecureByDefaultWithCustomContextPath()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class);
TestPropertyValues.of("management.context-path:/management")
.applyTo(this.context);
MockMvc mockMvc = createSecureMockMvc();
mockMvc.perform(get("/management/info")).andExpect(status().isOk());
mockMvc.perform(get("/management")).andExpect(status().isOk());
}
@Test
public void sensitiveEndpointsAreSecureByDefault() throws Exception {
public void endpointsAreSecureByDefault() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class);
MockMvc mockMvc = createSecureMockMvc();
@ -141,8 +120,7 @@ public class MvcEndpointIntegrationTests {
}
@Test
public void sensitiveEndpointsAreSecureByDefaultWithCustomContextPath()
throws Exception {
public void endpointsAreSecureByDefaultWithCustomContextPath() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class);
TestPropertyValues.of("management.context-path:/management")
@ -152,7 +130,7 @@ public class MvcEndpointIntegrationTests {
}
@Test
public void sensitiveEndpointsAreSecureWithNonActuatorRoleWithCustomContextPath()
public void endpointsAreSecureWithNonActuatorRoleWithCustomContextPath()
throws Exception {
TestSecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "N/A", "ROLE_USER"));
@ -165,7 +143,7 @@ public class MvcEndpointIntegrationTests {
}
@Test
public void sensitiveEndpointsAreSecureWithActuatorRoleWithCustomContextPath()
public void endpointsAreSecureWithActuatorRoleWithCustomContextPath()
throws Exception {
TestSecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR"));

View File

@ -89,22 +89,14 @@ public class MvcEndpointSecurityInterceptorTests {
}
@Test
public void endpointNotSensitiveShouldAllowAccess() throws Exception {
this.endpoint.setSensitive(false);
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isTrue();
}
@Test
public void sensitiveEndpointIfRoleIsPresentShouldAllowAccess() throws Exception {
public void endpointIfRoleIsPresentShouldAllowAccess() throws Exception {
this.servletContext.declareRoles("SUPER_HERO");
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isTrue();
}
@Test
public void sensitiveEndpointIfNotAuthenticatedShouldNotAllowAccess()
throws Exception {
public void endpointIfNotAuthenticatedShouldNotAllowAccess() throws Exception {
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isFalse();
verify(this.response).sendError(HttpStatus.UNAUTHORIZED.value(),
@ -118,8 +110,7 @@ public class MvcEndpointSecurityInterceptorTests {
}
@Test
public void sensitiveEndpointIfRoleIsNotCorrectShouldNotAllowAccess()
throws Exception {
public void endpointIfRoleIsNotCorrectShouldNotAllowAccess() throws Exception {
Principal principal = mock(Principal.class);
this.request.setUserPrincipal(principal);
this.servletContext.declareRoles("HERO");
@ -130,8 +121,7 @@ public class MvcEndpointSecurityInterceptorTests {
}
@Test
public void sensitiveEndpointIfRoleNotCorrectShouldCheckAuthorities()
throws Exception {
public void endpointIfRoleNotCorrectShouldCheckAuthorities() throws Exception {
Principal principal = mock(Principal.class);
this.request.setUserPrincipal(principal);
Authentication authentication = mock(Authentication.class);
@ -144,7 +134,7 @@ public class MvcEndpointSecurityInterceptorTests {
}
@Test
public void sensitiveEndpointIfRoleAndAuthoritiesNotCorrectShouldNotAllowAccess()
public void endpointIfRoleAndAuthoritiesNotCorrectShouldNotAllowAccess()
throws Exception {
Principal principal = mock(Principal.class);
this.request.setUserPrincipal(principal);

View File

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc;
import java.security.Principal;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -38,12 +36,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -65,19 +61,6 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests {
this.context.close();
}
@Test
public void healthWhenRightRoleNotPresentShouldNotExposeHealthDetails()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class);
this.context.refresh();
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/application/health").with(getRequestPostProcessor()))
.andExpect(status().isOk())
.andExpect(content().string("{\"status\":\"UP\"}"));
}
@Test
public void healthDetailPresent() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
@ -91,14 +74,6 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests {
"\"status\":\"UP\",\"test\":{\"status\":\"UP\",\"hello\":\"world\"}")));
}
private RequestPostProcessor getRequestPostProcessor() {
return (request) -> {
Principal principal = mock(Principal.class);
request.setUserPrincipal(principal);
return request;
};
}
@ImportAutoConfiguration({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class,

View File

@ -74,8 +74,7 @@ public class NoSpringSecurityMvcEndpointSecurityInterceptorTests {
}
@Test
public void sensitiveEndpointIfRoleNotPresentShouldNotValidateAuthorities()
throws Exception {
public void endpointIfRoleNotPresentShouldNotValidateAuthorities() throws Exception {
Principal principal = mock(Principal.class);
this.request.setUserPrincipal(principal);
this.servletContext.declareRoles("HERO");

View File

@ -1084,86 +1084,66 @@ content into your application; rather pick only the properties that you need.
# ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses)
endpoints.enabled=true # Enable endpoints.
endpoints.sensitive= # Default endpoint sensitive setting.
endpoints.auditevents.enabled= # Enable the endpoint.
endpoints.auditevents.path= # Endpoint path.
endpoints.auditevents.sensitive=false # Enable security on the endpoint.
endpoints.autoconfig.enabled= # Enable the endpoint.
endpoints.autoconfig.id= # Endpoint identifier.
endpoints.autoconfig.path= # Endpoint path.
endpoints.autoconfig.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.beans.enabled= # Enable the endpoint.
endpoints.beans.id= # Endpoint identifier.
endpoints.beans.path= # Endpoint path.
endpoints.beans.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.configprops.enabled= # Enable the endpoint.
endpoints.configprops.id= # Endpoint identifier.
endpoints.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions.
endpoints.configprops.path= # Endpoint path.
endpoints.configprops.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.docs.curies.enabled=false # Enable the curie generation.
endpoints.docs.enabled=true # Enable actuator docs endpoint.
endpoints.docs.path=/docs #
endpoints.docs.sensitive=false #
endpoints.dump.enabled= # Enable the endpoint.
endpoints.dump.id= # Endpoint identifier.
endpoints.dump.path= # Endpoint path.
endpoints.dump.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.env.enabled= # Enable the endpoint.
endpoints.env.id= # Endpoint identifier.
endpoints.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions.
endpoints.env.path= # Endpoint path.
endpoints.env.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.flyway.enabled= # Enable the endpoint.
endpoints.flyway.id= # Endpoint identifier.
endpoints.flyway.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.health.enabled= # Enable the endpoint.
endpoints.health.id= # Endpoint identifier.
endpoints.health.mapping.*= # Mapping of health statuses to HttpStatus codes. By default, registered health statuses map to sensible defaults (i.e. UP maps to 200).
endpoints.health.path= # Endpoint path.
endpoints.health.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.health.time-to-live=1000 # Time to live for cached result, in milliseconds.
endpoints.heapdump.enabled= # Enable the endpoint.
endpoints.heapdump.path= # Endpoint path.
endpoints.heapdump.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.info.enabled= # Enable the endpoint.
endpoints.info.id= # Endpoint identifier.
endpoints.info.path= # Endpoint path.
endpoints.info.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.jolokia.enabled=true # Enable Jolokia endpoint.
endpoints.jolokia.path=/jolokia # Endpoint URL path.
endpoints.jolokia.sensitive=true # Enable security on the endpoint.
endpoints.liquibase.enabled= # Enable the endpoint.
endpoints.liquibase.id= # Endpoint identifier.
endpoints.liquibase.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.logfile.enabled=true # Enable the endpoint.
endpoints.logfile.external-file= # External Logfile to be accessed.
endpoints.logfile.path=/logfile # Endpoint URL path.
endpoints.logfile.sensitive=true # Enable security on the endpoint.
endpoints.loggers.enabled=true # Enable the endpoint.
endpoints.loggers.id= # Endpoint identifier.
endpoints.loggers.path=/logfile # Endpoint path.
endpoints.loggers.sensitive=true # Mark if the endpoint exposes sensitive information.
endpoints.mappings.enabled= # Enable the endpoint.
endpoints.mappings.id= # Endpoint identifier.
endpoints.mappings.path= # Endpoint path.
endpoints.mappings.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.metrics.enabled= # Enable the endpoint.
endpoints.metrics.filter.enabled=true # Enable the metrics servlet filter.
endpoints.metrics.filter.gauge-submissions=merged # Http filter gauge submissions (merged, per-http-method)
endpoints.metrics.filter.counter-submissions=merged # Http filter counter submissions (merged, per-http-method)
endpoints.metrics.id= # Endpoint identifier.
endpoints.metrics.path= # Endpoint path.
endpoints.metrics.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.shutdown.enabled= # Enable the endpoint.
endpoints.shutdown.id= # Endpoint identifier.
endpoints.shutdown.path= # Endpoint path.
endpoints.shutdown.sensitive= # Mark if the endpoint exposes sensitive information.
endpoints.trace.enabled= # Enable the endpoint.
endpoints.trace.filter.enabled=true # Enable the trace servlet filter.
endpoints.trace.id= # Endpoint identifier.
endpoints.trace.path= # Endpoint path.
endpoints.trace.sensitive= # Mark if the endpoint exposes sensitive information.
# ENDPOINTS CORS CONFIGURATION ({sc-spring-boot-actuator}/autoconfigure/EndpointCorsProperties.{sc-ext}[EndpointCorsProperties])
endpoints.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported.

View File

@ -67,111 +67,142 @@ The following technology agnostic endpoints are available:
[cols="2,5,1"]
|===
| ID | Description | Sensitive Default
| ID | Description
|`auditevents`
|Exposes audit events information for the current application.
|true
|`autoconfig`
|Displays an auto-configuration report showing all auto-configuration candidates and the
reason why they '`were`' or '`were not`' applied.
|true
|`beans`
|Displays a complete list of all the Spring beans in your application.
|true
|`configprops`
|Displays a collated list of all `@ConfigurationProperties`.
|true
|`dump`
|Performs a thread dump.
|true
|`env`
|Exposes properties from Spring's `ConfigurableEnvironment`.
|true
|`flyway`
|Shows any Flyway database migrations that have been applied.
|true
|`health`
|Shows application health information (when the application is secure, a simple '`status`'
when accessed over an unauthenticated connection or full message details when
authenticated).
|false
|Shows application health information.
|`info`
|Displays arbitrary application info.
|false
|`loggers`
|Shows and modifies the configuration of loggers in the application.
|true
|`liquibase`
|Shows any Liquibase database migrations that have been applied.
|true
|`metrics`
|Shows '`metrics`' information for the current application.
|true
|`mappings`
|Displays a collated list of all `@RequestMapping` paths.
|true
|`shutdown`
|Allows the application to be gracefully shutdown (not enabled by default).
|true
|`trace`
|Displays trace information (by default the last 100 HTTP requests).
|true
|===
If you are using Spring MVC, the following additional endpoints can also be used:
[cols="2,5,1"]
|===
| ID | Description | Sensitive Default
| ID | Description
|`heapdump`
|Returns a GZip compressed `hprof` heap dump file.
|true
|`jolokia`
|Exposes JMX beans over HTTP (when Jolokia is on the classpath).
|true
|`logfile`
|Returns the contents of the logfile (if `logging.file` or `logging.path` properties have
been set). Supports the use of the HTTP `Range` header to retrieve part of the log file's
content.
|true
|===
NOTE: Depending on how an endpoint is exposed, the `sensitive` property may be used as
a security hint. For example, sensitive endpoints will require a username/password when
they are accessed over HTTP (or simply disabled if web security is not enabled).
[[production-ready-endpoints-security]]
=== Securing endpoints
By default all HTTP endpoints are secured such that only users that have an `ACTUATOR`
role may access them. Security is enforced using the standard
`HttpServletRequest.isUserInRole` method.
TIP: Use the `management.security.roles` property if you want something different to
`ACTUATOR`.
If you are deploying applications behind a firewall, you may prefer that all your actuator
endpoints can be accessed without requiring authentication. You can do this by changing
the `management.security.enabled` property:
.application.properties
[source,properties,indent=0]
----
management.security.enabled=false
----
NOTE: By default, actuator endpoints are exposed on the same port that serves regular
HTTP traffic. Take care not to accidentally expose sensitive information if you change
the `management.security.enabled` property.
If you're deploying applications publicly, you may want to add '`Spring Security`' to
handle user authentication. When '`Spring Security`' is added, by default '`basic`'
authentication will be used with the username `user` and a generated password (which is
printed on the console when the application starts).
TIP: Generated passwords are logged as the application starts. Search for '`Using default
security password`'.
You can use Spring properties to change the username and password and to change the
security role(s) required to access the endpoints. For example, you might set the following
in your `application.properties`:
[source,properties,indent=0]
----
security.user.name=admin
security.user.password=secret
management.security.roles=SUPERUSER
----
If your application has custom security configuration and you want all your actuator
endpoints to be accessible without authentication, you need to explicitly configure that
in your security configuration. Along with that, you need to change the
`management.security.enabled` property to `false`.
If your custom security configuration secures your actuator endpoints, you also need to
ensure that the authenticated user has the roles specified under
`management.security.roles`.
TIP: If you don't have a use case for exposing basic health information to unauthenticated
users, and you have secured the actuator endpoints with custom security, you can set
`management.security.enabled` to `false`. This will inform Spring Boot to skip the
additional role check.
[[production-ready-customizing-endpoints]]
=== Customizing endpoints
Endpoints can be customized using Spring properties. You can change if an endpoint is
`enabled`, if it is considered `sensitive` and even its `id`.
`enabled` and its `id`.
For example, here is an `application.properties` that changes the sensitivity and id
of the `beans` endpoint and also enables `shutdown`.
For example, here is an `application.properties` that changes the id of the `beans`
endpoint and also enables `shutdown`.
[source,properties,indent=0]
----
endpoints.beans.id=springbeans
endpoints.beans.sensitive=false
endpoints.shutdown.enabled=true
----
@ -188,16 +219,6 @@ For example, the following will disable _all_ endpoints except for `info`:
endpoints.info.enabled=true
----
Likewise, you can also choose to globally set the "`sensitive`" flag of all endpoints. By
default, the sensitive flag depends on the type of endpoint (see the table above).
For example, to mark _all_ endpoints as sensitive except `info`:
[source,properties,indent=0]
----
endpoints.sensitive=true
endpoints.info.sensitive=false
----
[[production-ready-endpoint-hypermedia]]
@ -272,12 +293,6 @@ overall health status. If no `HealthIndicator` returns a status that is known to
=== Security with HealthIndicators
Information returned by `HealthIndicators` is often somewhat sensitive in nature. For
example, you probably don't want to publish details of your database server to the
world. For this reason, by default, only the health status is exposed over an
unauthenticated HTTP connection. If you are happy for complete health information to always
be exposed you can set `endpoints.health.sensitive` to `false`.
Health responses are also cached to prevent "`denial of service`" attacks. Use the
`endpoints.health.time-to-live` property if you want to change the default cache period
of 1000 milliseconds.
@ -545,64 +560,6 @@ is exposed as `/application/health`.
[[production-ready-sensitive-endpoints]]
=== Accessing sensitive endpoints
By default all sensitive HTTP endpoints are secured such that only users that have an
`ACTUATOR` role may access them. Security is enforced using the standard
`HttpServletRequest.isUserInRole` method.
TIP: Use the `management.security.roles` property if you want something different to
`ACTUATOR`.
If you are deploying applications behind a firewall, you may prefer that all your actuator
endpoints can be accessed without requiring authentication. You can do this by changing
the `management.security.enabled` property:
.application.properties
[source,properties,indent=0]
----
management.security.enabled=false
----
NOTE: By default, actuator endpoints are exposed on the same port that serves regular
HTTP traffic. Take care not to accidentally expose sensitive information if you change
the `management.security.enabled` property.
If you're deploying applications publicly, you may want to add '`Spring Security`' to
handle user authentication. When '`Spring Security`' is added, by default '`basic`'
authentication will be used with the username `user` and a generated password (which is
printed on the console when the application starts).
TIP: Generated passwords are logged as the application starts. Search for '`Using default
security password`'.
You can use Spring properties to change the username and password and to change the
security role(s) required to access the endpoints. For example, you might set the following
in your `application.properties`:
[source,properties,indent=0]
----
security.user.name=admin
security.user.password=secret
management.security.roles=SUPERUSER
----
If your application has custom security configuration and you want all your actuator endpoints
to be accessible without authentication, you need to explicitly configure that in your
security configuration. Along with that, you need to change the `management.security.enabled`
property to `false`.
If your custom security configuration secures your actuator endpoints, you also need to ensure that
the authenticated user has the roles specified under `management.security.roles`.
TIP: If you don't have a use case for exposing basic health information to unauthenticated users,
and you have secured the actuator endpoints with custom security, you can set `management.security.enabled`
to `false`. This will inform Spring Boot to skip the additional role check.
[[production-ready-customizing-management-server-context-path]]
=== Customizing the management endpoint paths
Sometimes it is useful to customize the prefix for the management endpoints.
@ -763,7 +720,7 @@ Sample summarized HTTP response for status "DOWN" (notice the 503 status code):
{"status":"DOWN"}
----
Sample detailed HTTP response:
Sample detailed HTTP response:
[source,indent=0]
----
@ -789,30 +746,6 @@ Sample detailed HTTP response:
}
----
The above-described restrictions can be enhanced, thereby allowing only authenticated
users full access to the health endpoint in a secure application. To do so, set
`endpoints.health.sensitive` to `true`. Here's a summary of behavior (with default
`sensitive` flag value "`false`" indicated in bold):
|====
| `management.security.enabled` | `endpoints.health.sensitive` | Unauthenticated | Authenticated (with right role)
|false
|*
|Full content
|Full content
|true
|**false**
|Status only
|Full content
|true
|true
|No content
|Full content
|====
[[production-ready-jmx]]

View File

@ -50,14 +50,14 @@ public class CorsSampleActuatorApplicationTests {
}
@Test
public void sensitiveEndpointShouldReturnUnauthorized() throws Exception {
public void endpointShouldReturnUnauthorized() throws Exception {
ResponseEntity<?> entity = this.testRestTemplate.getForEntity("/application/env",
Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void preflightRequestForInsensitiveShouldReturnOk() throws Exception {
public void preflightRequestToEndpointShouldReturnOk() throws Exception {
RequestEntity<?> healthRequest = RequestEntity
.options(new URI("/application/health"))
.header("Origin", "http://localhost:8080")
@ -67,15 +67,6 @@ public class CorsSampleActuatorApplicationTests {
assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void preflightRequestForSensitiveEndpointShouldReturnOk() throws Exception {
RequestEntity<?> entity = RequestEntity.options(new URI("/application/env"))
.header("Origin", "http://localhost:8080")
.header("Access-Control-Request-Method", "GET").build();
ResponseEntity<?> env = this.testRestTemplate.exchange(entity, Map.class);
assertThat(env.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden()
throws Exception {

View File

@ -21,7 +21,9 @@ import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -45,6 +47,9 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class ManagementAddressActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -61,11 +66,16 @@ public class ManagementAddressActuatorApplicationTests {
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/health",
String.class);
ResponseEntity<String> entity = new TestRestTemplate()
.withBasicAuth("user", getPassword())
.getForEntity("http://localhost:" + this.managementPort + "/admin/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}
private String getPassword() {
return this.security.getUser().getPassword();
}
}

View File

@ -22,6 +22,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -46,10 +47,14 @@ public class ManagementPathSampleActuatorApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties securityProperties;
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/admin/health",
String.class);
ResponseEntity<String> entity = this.restTemplate
.withBasicAuth("user", getPassword())
.getForEntity("/admin/health", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}
@ -65,4 +70,8 @@ public class ManagementPathSampleActuatorApplicationTests {
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
}
private String getPassword() {
return this.securityProperties.getUser().getPassword();
}
}

View File

@ -77,9 +77,10 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/health",
String.class);
ResponseEntity<String> entity = new TestRestTemplate()
.withBasicAuth("user", getPassword())
.getForEntity("http://localhost:" + this.managementPort + "/admin/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}

View File

@ -71,14 +71,17 @@ public class ManagementPortSampleActuatorApplicationTests {
testHome(); // makes sure some requests have been made
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/application/metrics", Map.class);
"http://localhost:" + this.managementPort + "/application/metrics",
Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/application/health", String.class);
ResponseEntity<String> entity = new TestRestTemplate()
.withBasicAuth("user", getPassword()).getForEntity(
"http://localhost:" + this.managementPort + "/application/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-2017 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 sample.actuator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for /health with {@code endpoints.health.sensitive=false}.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"endpoints.health.sensitive=false" })
@DirtiesContext
public class NonSensitiveHealthTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testSecureHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/application/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).doesNotContain("\"hello\":1");
}
}

View File

@ -121,24 +121,17 @@ public class SampleActuatorApplicationTests {
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate
.withBasicAuth("user", getPassword())
.getForEntity("/application/health", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
assertThat(entity.getBody()).doesNotContain("\"hello\":\"1\"");
}
@Test
public void testSecureHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate
.withBasicAuth("user", getPassword())
.getForEntity("/application/health", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"hello\":1");
}
@Test
public void testInfo() throws Exception {
ResponseEntity<String> entity = this.restTemplate
.withBasicAuth("user", getPassword())
.getForEntity("/application/info", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody())

View File

@ -22,6 +22,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -46,6 +47,9 @@ public class ServletPathSampleActuatorApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties security;
@Test
public void testErrorPath() throws Exception {
@SuppressWarnings("rawtypes")
@ -60,8 +64,9 @@ public class ServletPathSampleActuatorApplicationTests {
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/spring//application/health",
String.class);
ResponseEntity<String> entity = this.restTemplate
.withBasicAuth("user", getPassword())
.getForEntity("/spring/application/health", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}
@ -78,4 +83,8 @@ public class ServletPathSampleActuatorApplicationTests {
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
}
private String getPassword() {
return this.security.getUser().getPassword();
}
}