Add a property to disable the /actuator discovery page

Closes gh-24693
This commit is contained in:
Madhura Bhave 2021-02-09 11:36:29 -08:00
parent 3610fe50b7
commit c05cb21ab7
8 changed files with 106 additions and 34 deletions

View File

@ -51,6 +51,8 @@ public class WebEndpointProperties {
*/
private final Map<String, String> 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;
}
}
}

View File

@ -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));
}
/**

View File

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

View File

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

View File

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

View File

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

View File

@ -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());
}

View File

@ -425,6 +425,17 @@ NOTE: The prefix `management.endpoint.<name>` 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.