Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public enum DefaultIncludes {
/**
* The default set of include patterns used for web.
*/
WEB("info", "health");
WEB("health");

private final EndpointPatterns patterns;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
Expand All @@ -38,10 +37,8 @@

/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security when actuator is
* on the classpath. It allows unauthenticated access to the {@link HealthEndpoint} and
* {@link InfoEndpoint}. If the user specifies their own
* {@link org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
* WebSecurityConfigurerAdapter} or {@link SecurityFilterChain} bean, this will back-off
* on the classpath. It allows unauthenticated access to the {@link HealthEndpoint}. If
* the user specifies their own{@link SecurityFilterChain} bean, this will back-off
* completely and the user should specify all the bits that they want to configure as part
* of the custom security configuration.
*
Expand All @@ -60,7 +57,7 @@ public class ManagementWebSecurityAutoConfiguration {
@Bean
SecurityFilterChain managementSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll();
requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll();
requests.anyRequest().authenticated();
});
http.formLogin(Customizer.withDefaults());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@
{
"name": "management.endpoints.web.exposure.include",
"defaultValue": [
"health",
"info"
"health"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class ConditionalOnAvailableEndpointTests {

@Test
void outcomeShouldMatchDefaults() {
this.contextRunner.run((context) -> assertThat(context).hasBean("info").hasBean("health")
.doesNotHaveBean("spring").doesNotHaveBean("test").doesNotHaveBean("shutdown"));
this.contextRunner.run((context) -> assertThat(context).hasBean("health").doesNotHaveBean("spring")
.doesNotHaveBean("test").doesNotHaveBean("shutdown"));
}

@Test
Expand Down Expand Up @@ -79,24 +79,24 @@ void outcomeWhenIncludeAllWebAndEnablingEndpointDisabledByDefaultShouldMatchAll(
@Test
void outcomeWhenIncludeAllJmxButJmxDisabledShouldMatchDefaults() {
this.contextRunner.withPropertyValues("management.endpoints.jmx.exposure.include=*")
.run((context) -> assertThat(context).hasBean("info").hasBean("health").doesNotHaveBean("spring")
.run((context) -> assertThat(context).hasBean("health").doesNotHaveBean("spring")
.doesNotHaveBean("test").doesNotHaveBean("shutdown"));
}

@Test
void outcomeWhenIncludeAllJmxAndJmxEnabledShouldMatchEnabledEndpoints() {
this.contextRunner.withPropertyValues("management.endpoints.jmx.exposure.include=*", "spring.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("info").hasBean("health").hasBean("test")
.hasBean("spring").doesNotHaveBean("shutdown"));
.run((context) -> assertThat(context).hasBean("health").hasBean("test").hasBean("spring")
.doesNotHaveBean("shutdown"));
}

@Test
void outcomeWhenIncludeAllJmxAndJmxEnabledAndEnablingEndpointDisabledByDefaultShouldMatchAll() {
this.contextRunner
.withPropertyValues("management.endpoints.jmx.exposure.include=*", "spring.jmx.enabled=true",
"management.endpoint.shutdown.enabled=true")
.run((context) -> assertThat(context).hasBean("info").hasBean("health").hasBean("test")
.hasBean("spring").hasBean("shutdown"));
.run((context) -> assertThat(context).hasBean("health").hasBean("test").hasBean("spring")
.hasBean("shutdown"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,6 @@ class InfoEndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(InfoEndpointAutoConfiguration.class));

@Test
void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.shutdown.enabled:true")
.run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class));
}

@Test
void runShouldHaveEndpointBeanEvenIfDefaultIsDisabled() {
this.contextRunner.withPropertyValues("management.endpoint.default.enabled:false")
.run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class));
}

@Test
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.info.enabled:false")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void webEndpointsAreDisabledByDefault() {
assertThat(isExposed(client, HttpMethod.GET, "customservlet")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "env")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue();
assertThat(isExposed(client, HttpMethod.GET, "info")).isTrue();
assertThat(isExposed(client, HttpMethod.GET, "info")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "mappings")).isFalse();
assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "threaddump")).isFalse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ void permitAllForHealth() {
this.contextRunner.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull());
}

@Test
void permitAllForInfo() {
this.contextRunner.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/info")).isNull());
}

@Test
void securesEverythingElse() {
this.contextRunner.run((context) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,6 @@ void permitAllForHealth() {
});
}

@Test
void permitAllForInfo() {
this.contextRunner.run((context) -> {
HttpStatus status = getResponseStatus(context, "/actuator/info");
assertThat(status).isEqualTo(HttpStatus.OK);
});
}

@Test
void securesEverythingElse() {
this.contextRunner.run((context) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3960,10 +3960,10 @@ You can register multiple relying parties under the `spring.security.saml2.relyi

[[boot-features-security-actuator]]
=== Actuator Security
For security purposes, all actuators other than `/health` and `/info` are disabled by default.
For security purposes, all actuators other than `/health` are disabled by default.
The configprop:management.endpoints.web.exposure.include[] property can be used to enable the actuators.

If Spring Security is on the classpath and no other `WebSecurityConfigurerAdapter` or `SecurityFilterChain` bean is present, all actuators other than `/health` and `/info` are secured by Spring Boot auto-configuration.
If Spring Security is on the classpath and no other `WebSecurityConfigurerAdapter` or `SecurityFilterChain` bean is present, all actuators other than `/health` are secured by Spring Boot auto-configuration.
If you define a custom `WebSecurityConfigurerAdapter` or `SecurityFilterChain` bean, Spring Boot auto-configuration will back off and you will be in full control of actuator access rules.

NOTE: Before setting the `management.endpoints.web.exposure.include`, ensure that the exposed actuators do not contain sensitive information and/or are secured by placing them behind a firewall or by something like Spring Security.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private UserDetails createUserDetails(String username, String password, String..
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.mvcMatchers("/actuator/beans").hasRole("BEANS");
requests.requestMatchers(EndpointRequest.to("health", "info")).permitAll();
requests.requestMatchers(EndpointRequest.to("health")).permitAll();
requests.requestMatchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class))
.hasRole("ACTUATOR");
requests.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,6 @@ void healthInsecureByDefault() {
assertThat(entity.getBody()).doesNotContain("\"hello\":\"1\"");
}

@Test
void infoInsecureByDefault() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/actuator/info", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"artifact\":\"spring-boot-smoke-test-actuator\"");
assertThat(entity.getBody()).contains("\"someKey\":\"someValue\"");
assertThat(entity.getBody()).contains("\"java\":{", "\"source\":\"1.8\"", "\"target\":\"1.8\"");
assertThat(entity.getBody()).contains("\"encoding\":{", "\"source\":\"UTF-8\"", "\"reporting\":\"UTF-8\"");
}

@Test
void testErrorPage() {
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", "password").getForEntity("/foo",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
SecurityFilterChain configure(HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeRequests()
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.requestMatchers(EndpointRequest.to("health")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class)).hasRole("ACTUATOR")
.antMatchers("/**").hasRole("USER")
.and()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ static class SecurityConfiguration {
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.authorizeExchange((exchanges) -> {
exchanges.matchers(EndpointRequest.to("health", "info")).permitAll();
exchanges.matchers(EndpointRequest.to("health")).permitAll();
exchanges.matchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class))
.hasRole("ACTUATOR");
exchanges.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ void userDefinedMappingsSecure() {
}

@Test
void healthAndInfoDoNotRequireAuthentication() {
void healthDoesNotRequireAuthentication() {
this.webClient.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk();
this.webClient.get().uri("/actuator/info").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk();
}

@Test
Expand Down Expand Up @@ -117,7 +116,7 @@ MapReactiveUserDetailsService userDetailsService() {
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.authorizeExchange((exchanges) -> {
exchanges.matchers(EndpointRequest.to("health", "info")).permitAll();
exchanges.matchers(EndpointRequest.to("health")).permitAll();
exchanges.matchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class))
.hasRole("ACTUATOR");
exchanges.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
Expand Down