From 53c1e79810888c9cdc7f74aab353f7b1b91eead6 Mon Sep 17 00:00:00 2001 From: Hatef Palizgar Date: Thu, 21 Jan 2021 13:31:53 -0800 Subject: [PATCH] Change info endpoint to be secure and unexposed by default See gh-24715 --- .../expose/IncludeExcludeEndpointFilter.java | 2 +- .../ManagementWebSecurityAutoConfiguration.java | 9 +++------ .../additional-spring-configuration-metadata.json | 3 +-- .../ConditionalOnAvailableEndpointTests.java | 10 +++++----- .../info/InfoEndpointAutoConfigurationTests.java | 12 ------------ .../WebMvcEndpointExposureIntegrationTests.java | 2 +- ...eManagementWebSecurityAutoConfigurationTests.java | 5 ----- .../ManagementWebSecurityAutoConfigurationTests.java | 8 -------- .../src/docs/asciidoc/spring-boot-features.adoc | 4 ++-- .../customsecurity/SecurityConfiguration.java | 2 +- .../actuator/SampleActuatorApplicationTests.java | 10 ---------- .../secure/jersey/SecurityConfiguration.java | 2 +- .../ManagementPortSampleSecureWebFluxTests.java | 2 +- .../SampleSecureWebFluxCustomSecurityTests.java | 5 ++--- 14 files changed, 18 insertions(+), 58 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java index 970db5bacb..d71d696429 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java @@ -178,7 +178,7 @@ public class IncludeExcludeEndpointFilter> implem /** * The default set of include patterns used for web. */ - WEB("info", "health"); + WEB("health"); private final EndpointPatterns patterns; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java index 4641195173..f43c7dfcdc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java @@ -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()); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3aa62c3716..1dc01fa697 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -91,8 +91,7 @@ { "name": "management.endpoints.web.exposure.include", "defaultValue": [ - "health", - "info" + "health" ] }, { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java index 9230e66ae7..417a3cbe01 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java @@ -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 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfigurationTests.java index d76c253dfa..c0c3a80299 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfigurationTests.java @@ -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") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index 61dd70d553..de4711434f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -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(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index ede4d57e68..6fb0e8566f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -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) -> { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java index 23e24f7693..6dc70733ea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java @@ -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) -> { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index dd50d22f92..5fefb40a9e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -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. diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/main/java/smoketest/actuator/customsecurity/SecurityConfiguration.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/main/java/smoketest/actuator/customsecurity/SecurityConfiguration.java index 9ce59217ce..1e3f35bb0c 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/main/java/smoketest/actuator/customsecurity/SecurityConfiguration.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/main/java/smoketest/actuator/customsecurity/SecurityConfiguration.java @@ -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(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationTests.java index 3f20fee8fd..42c3c15873 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationTests.java @@ -110,16 +110,6 @@ class SampleActuatorApplicationTests { assertThat(entity.getBody()).doesNotContain("\"hello\":\"1\""); } - @Test - void infoInsecureByDefault() { - ResponseEntity 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 entity = this.restTemplate.withBasicAuth("user", "password").getForEntity("/foo", diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-jersey/src/main/java/smoketest/secure/jersey/SecurityConfiguration.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-jersey/src/main/java/smoketest/secure/jersey/SecurityConfiguration.java index e4fd4dd24b..1680ad97c5 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-jersey/src/main/java/smoketest/secure/jersey/SecurityConfiguration.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-jersey/src/main/java/smoketest/secure/jersey/SecurityConfiguration.java @@ -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() diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/ManagementPortSampleSecureWebFluxTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/ManagementPortSampleSecureWebFluxTests.java index 87c37fa6c3..f85dd38774 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/ManagementPortSampleSecureWebFluxTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/ManagementPortSampleSecureWebFluxTests.java @@ -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(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java index 6575c61ce8..f9d535b9a6 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-secure-webflux/src/test/java/smoketest/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java @@ -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();