Change info endpoint to be secure and unexposed by default

See gh-24715
pull/24982/head
Hatef Palizgar 4 years ago committed by Madhura Bhave
parent 0fc33b020d
commit 53c1e79810

@ -178,7 +178,7 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
/**
* The default set of include patterns used for web.
*/
WEB("info", "health");
WEB("health");
private final EndpointPatterns patterns;

@ -20,7 +20,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu
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;
@ -38,10 +37,8 @@ import org.springframework.security.web.SecurityFilterChain;
/**
* {@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.
*
@ -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());

@ -91,8 +91,7 @@
{
"name": "management.endpoints.web.exposure.include",
"defaultValue": [
"health",
"info"
"health"
]
},
{

@ -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
@ -79,7 +79,7 @@ class ConditionalOnAvailableEndpointTests {
@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"));
}
@ -95,8 +95,8 @@ class ConditionalOnAvailableEndpointTests {
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

@ -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")

@ -91,7 +91,7 @@ class WebMvcEndpointExposureIntegrationTests {
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();

@ -78,11 +78,6 @@ class ReactiveManagementWebSecurityAutoConfigurationTests {
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) -> {

@ -73,14 +73,6 @@ class ManagementWebSecurityAutoConfigurationTests {
});
}
@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) -> {

@ -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.

@ -57,7 +57,7 @@ public class SecurityConfiguration {
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();

@ -110,16 +110,6 @@ class SampleActuatorApplicationTests {
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",

@ -42,7 +42,7 @@ public class SecurityConfiguration {
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()

@ -92,7 +92,7 @@ class ManagementPortSampleSecureWebFluxTests {
@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();

@ -55,10 +55,9 @@ class SampleSecureWebFluxCustomSecurityTests {
}
@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
@ -117,7 +116,7 @@ class SampleSecureWebFluxCustomSecurityTests {
@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();

Loading…
Cancel
Save