From c05cb21ab7839048432b9b0616500d921843c21e Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 9 Feb 2021 11:36:29 -0800 Subject: [PATCH] Add a property to disable the `/actuator` discovery page Closes gh-24693 --- .../endpoint/web/WebEndpointProperties.java | 23 ++++++++++++ ...ndpointManagementContextConfiguration.java | 10 +++--- ...ndpointManagementContextConfiguration.java | 9 ++--- ...ndpointManagementContextConfiguration.java | 9 +++-- .../JerseyEndpointIntegrationTests.java | 35 +++++++++++++------ .../WebFluxEndpointIntegrationTests.java | 34 +++++++++++------- .../WebMvcEndpointIntegrationTests.java | 9 +++++ .../asciidoc/production-ready-features.adoc | 11 ++++++ 8 files changed, 106 insertions(+), 34 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointProperties.java index c8f87b501d..e997a343f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointProperties.java @@ -51,6 +51,8 @@ public class WebEndpointProperties { */ private final Map pathMapping = new LinkedHashMap<>(); + private final Discovery discovery = new Discovery(); + public Exposure getExposure() { return this.exposure; } @@ -75,6 +77,10 @@ public class WebEndpointProperties { return this.pathMapping; } + public Discovery getDiscovery() { + return this.discovery; + } + public static class Exposure { /** @@ -105,4 +111,21 @@ public class WebEndpointProperties { } + public static class Discovery { + + /** + * Whether the discovery page is enabled. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java index 3d18efd431..09a2d596bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java @@ -71,15 +71,15 @@ class JerseyWebEndpointManagementContextConfiguration { ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, WebEndpointProperties webEndpointProperties) { String basePath = webEndpointProperties.getBasePath(); - boolean shouldRegisterLinks = shouldRegisterLinksMapping(environment, basePath); - shouldRegisterLinksMapping(environment, basePath); + boolean shouldRegisterLinks = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); return new JerseyWebEndpointsResourcesRegistrar(resourceConfig.getIfAvailable(), webEndpointsSupplier, servletEndpointsSupplier, endpointMediaTypes, basePath, shouldRegisterLinks); } - private boolean shouldRegisterLinksMapping(Environment environment, String basePath) { - return StringUtils.hasText(basePath) - || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT); + private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Environment environment, + String basePath) { + return properties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) + || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java index 2c2571d20c..c7900363b4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/reactive/WebFluxEndpointManagementContextConfiguration.java @@ -75,12 +75,13 @@ public class WebFluxEndpointManagementContextConfiguration { allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); return new WebFluxEndpointHandlerMapping(endpointMapping, endpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), - shouldRegisterLinksMapping(environment, basePath)); + shouldRegisterLinksMapping(webEndpointProperties, environment, basePath)); } - private boolean shouldRegisterLinksMapping(Environment environment, String basePath) { - return StringUtils.hasText(basePath) - || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT); + private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Environment environment, + String basePath) { + return properties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) + || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java index 73a2bc0db4..dd0629a596 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java @@ -74,13 +74,18 @@ public class WebMvcEndpointManagementContextConfiguration { allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); String basePath = webEndpointProperties.getBasePath(); EndpointMapping endpointMapping = new EndpointMapping(basePath); - boolean shouldRegisterLinksMapping = StringUtils.hasText(basePath) - || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT); + boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping); } + private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, + String basePath) { + return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) + || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); + } + @Bean @ConditionalOnMissingBean public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java index 129bfb28c8..b275a6729c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java @@ -50,29 +50,44 @@ class JerseyEndpointIntegrationTests { testJerseyEndpoints(new Class[] { EndpointsConfiguration.class, ResourceConfigConfiguration.class }); } + @Test + void linksPageIsNotAvailableWhenDisabled() { + getContextRunner(new Class[] { EndpointsConfiguration.class, ResourceConfigConfiguration.class }) + .withPropertyValues("management.endpoints.web.discovery.enabled:false").run((context) -> { + int port = context + .getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) + .getWebServer().getPort(); + WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); + client.get().uri("/actuator").exchange().expectStatus().isNotFound(); + }); + } + @Test void actuatorEndpointsWhenUserProvidedResourceConfigBeanNotAvailable() { testJerseyEndpoints(new Class[] { EndpointsConfiguration.class }); } protected void testJerseyEndpoints(Class[] userConfigurations) { + getContextRunner(userConfigurations).run((context) -> { + int port = context.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) + .getWebServer().getPort(); + WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); + client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") + .isNotEmpty().jsonPath("_links.restcontroller").doesNotExist().jsonPath("_links.controller") + .doesNotExist(); + }); + } + + WebApplicationContextRunner getContextRunner(Class[] userConfigurations) { FilteredClassLoader classLoader = new FilteredClassLoader(DispatcherServlet.class); - new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + return new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) .withClassLoader(classLoader) .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, JerseyAutoConfiguration.class, EndpointAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class, WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class)) .withUserConfiguration(userConfigurations) - .withPropertyValues("management.endpoints.web.exposure.include:*", "server.port:0").run((context) -> { - int port = context - .getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class) - .getWebServer().getPort(); - WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); - client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") - .isNotEmpty().jsonPath("_links.restcontroller").doesNotExist().jsonPath("_links.controller") - .doesNotExist(); - }); + .withPropertyValues("management.endpoints.web.exposure.include:*", "server.port:0"); } @ControllerEndpoint(id = "controller") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java index 321e9dd8de..71ffcc35d7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointIntegrationTests.java @@ -43,21 +43,29 @@ import org.springframework.test.web.reactive.server.WebTestClient; */ class WebFluxEndpointIntegrationTests { + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, CodecsAutoConfiguration.class, + WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class, EndpointAutoConfiguration.class, + WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, + ReactiveManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class)) + .withUserConfiguration(EndpointsConfiguration.class); + @Test void linksAreProvidedToAllEndpointTypes() throws Exception { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, CodecsAutoConfiguration.class, - WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class, - EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - ManagementContextAutoConfiguration.class, ReactiveManagementContextAutoConfiguration.class, - BeansEndpointAutoConfiguration.class)) - .withUserConfiguration(EndpointsConfiguration.class) - .withPropertyValues("management.endpoints.web.exposure.include:*").run((context) -> { - WebTestClient client = createWebTestClient(context); - client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") - .isNotEmpty().jsonPath("_links.restcontroller").isNotEmpty().jsonPath("_links.controller") - .isNotEmpty(); - }); + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include:*").run((context) -> { + WebTestClient client = createWebTestClient(context); + client.get().uri("/actuator").exchange().expectStatus().isOk().expectBody().jsonPath("_links.beans") + .isNotEmpty().jsonPath("_links.restcontroller").isNotEmpty().jsonPath("_links.controller") + .isNotEmpty(); + }); + } + + @Test + void linksPageIsNotAvailableWhenDisabled() throws Exception { + this.contextRunner.withPropertyValues("management.endpoints.web.discovery.enabled=false").run((context) -> { + WebTestClient client = createWebTestClient(context); + client.get().uri("/actuator").exchange().expectStatus().isNotFound(); + }); } private WebTestClient createWebTestClient(ApplicationContext context) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java index 35a9e9cf3e..ec273406c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointIntegrationTests.java @@ -119,6 +119,15 @@ class WebMvcEndpointIntegrationTests { both(hasKey("beans")).and(hasKey("servlet")).and(hasKey("restcontroller")).and(hasKey("controller")))); } + @Test + void linksPageIsNotAvailableWhenDisabled() throws Exception { + this.context = new AnnotationConfigServletWebApplicationContext(); + this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class); + TestPropertyValues.of("management.endpoints.web.discovery.enabled=false").applyTo(this.context); + MockMvc mockMvc = doCreateMockMvc(); + mockMvc.perform(get("/actuator").accept("*/*")).andExpect(status().isNotFound()); + } + private MockMvc createSecureMockMvc() { return doCreateMockMvc(springSecurity()); } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc index 1137f6fe6f..088a796838 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc @@ -425,6 +425,17 @@ NOTE: The prefix `management.endpoint.` is used to uniquely identify the e A "`discovery page`" is added with links to all the endpoints. The "`discovery page`" is available on `/actuator` by default. +To disable the "`discovery page`", add the following property to your application properties: + +[source,yaml,indent=0,configprops,configblocks] +---- + management: + endpoints: + web: + discovery: + enabled: false +---- + When a custom management context path is configured, the "`discovery page`" automatically moves from `/actuator` to the root of the management context. For example, if the management context path is `/management`, then the discovery page is available from `/management`. When the management context path is set to `/`, the discovery page is disabled to prevent the possibility of a clash with other mappings.