From 8f535b266cc4abcd4d5dea8f08821dca3711df2a Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Tue, 22 Apr 2025 15:54:56 +0900 Subject: [PATCH 1/2] Fail fast when base path and an endpoint mapping are set to '/' Throw an exception if actuator is running on the main server port and the base path and an individual mapping are set to '/'. See gh-45251 Signed-off-by: yongjunhong --- ...ndpointManagementContextConfiguration.java | 13 +++++++++ .../ManagementEndpointConflictSmokeTest.java | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java 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 e5a5e0bdf2e..a479565b1a0 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 @@ -69,6 +69,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * * @author Andy Wilkinson * @author Phillip Webb + * @author Yongjun Hong * @since 2.0.0 */ @ManagementContextConfiguration(proxyBeanMethods = false) @@ -93,6 +94,18 @@ public class WebMvcEndpointManagementContextConfiguration { allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); String basePath = webEndpointProperties.getBasePath(); EndpointMapping endpointMapping = new EndpointMapping(basePath); + + if (basePath.isEmpty() && ManagementPortType.get(environment).equals(ManagementPortType.SAME)) { + for (ExposableWebEndpoint endpoint : webEndpoints) { + if ("/".equals(endpoint.getRootPath())) { + throw new IllegalStateException( + "Management endpoints and endpoint path are both mapped to '/' on the server port which will " + + "block access to other endpoints. Please use a different path for management endpoints or " + + "map them to a dedicated management port."); + } + } + } + boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java new file mode 100644 index 00000000000..8cfa7ce64bc --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java @@ -0,0 +1,27 @@ +package smoketest.actuator; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.SpringApplication; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Verifies that an exception is thrown when management and server endpoint paths + * conflict. + * + * @author Yongjun Hong + */ +class ManagementEndpointConflictSmokeTest { + + @Test + void shouldThrowExceptionWhenManagementAndServerPathsConflict() { + assertThatThrownBy(() -> { + SpringApplication.run(SampleActuatorApplication.class, "--management.endpoints.web.base-path=/", + "--management.endpoints.web.path-mapping.health=/"); + }).isInstanceOf(BeanCreationException.class) + .hasMessageContaining("Management endpoints and endpoint path are both mapped to '/'"); + } + +} \ No newline at end of file From 7dac8ca3458a9d74d3f1b91ef04d408a44425fe2 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 22 Apr 2025 11:48:14 -0700 Subject: [PATCH 2/2] Polish 'Fail fast when base path and an endpoint mapping are set to '/'' See gh-45251 --- .../web/WebEndpointAutoConfiguration.java | 35 +++++++++++++++- ...ndpointManagementContextConfiguration.java | 15 +------ .../ManagementContextAutoConfiguration.java | 5 +-- .../web/annotation/DiscoveredWebEndpoint.java | 5 ++- .../ManagementEndpointConflictSmokeTest.java | 27 ------------ .../ManagementEndpointConflictSmokeTests.java | 42 +++++++++++++++++++ 6 files changed, 81 insertions(+), 48 deletions(-) delete mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index 0d37cb7af00..f46f71f96e9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; @@ -33,10 +34,12 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -47,6 +50,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for web {@link Endpoint @Endpoint} @@ -54,6 +59,7 @@ import org.springframework.context.annotation.Configuration; * * @author Phillip Webb * @author Stephane Nicoll + * @author Yongjun Hong * @since 2.0.0 */ @AutoConfiguration(after = EndpointAutoConfiguration.class) @@ -109,7 +115,32 @@ public class WebEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public PathMappedEndpoints pathMappedEndpoints(Collection> endpointSuppliers) { - return new PathMappedEndpoints(this.properties.getBasePath(), endpointSuppliers); + String basePath = this.properties.getBasePath(); + PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints(basePath, endpointSuppliers); + if ((!StringUtils.hasText(basePath) || "/".equals(basePath)) + && ManagementPortType.get(this.applicationContext.getEnvironment()) == ManagementPortType.SAME) { + assertHasNoRootPaths(pathMappedEndpoints); + } + return pathMappedEndpoints; + } + + private void assertHasNoRootPaths(PathMappedEndpoints endpoints) { + for (PathMappedEndpoint endpoint : endpoints) { + if (endpoint instanceof ExposableWebEndpoint webEndpoint) { + Assert.state(!isMappedToRootPath(webEndpoint), + () -> "Management base path and the '" + webEndpoint.getEndpointId() + + "' actuator endpoint are both mapped to '/' " + + "on the server port which will block access to other endpoints. " + + "Please use a different path for management endpoints or map them to a " + + "dedicated management port."); + } + + } + } + + private boolean isMappedToRootPath(PathMappedEndpoint endpoint) { + return endpoint.getRootPath().equals("/") + || endpoint.getAdditionalPaths(WebServerNamespace.SERVER).contains("/"); } @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 a479565b1a0..b3f288ba8f9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,7 +69,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * * @author Andy Wilkinson * @author Phillip Webb - * @author Yongjun Hong * @since 2.0.0 */ @ManagementContextConfiguration(proxyBeanMethods = false) @@ -94,18 +93,6 @@ public class WebMvcEndpointManagementContextConfiguration { allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); String basePath = webEndpointProperties.getBasePath(); EndpointMapping endpointMapping = new EndpointMapping(basePath); - - if (basePath.isEmpty() && ManagementPortType.get(environment).equals(ManagementPortType.SAME)) { - for (ExposableWebEndpoint endpoint : webEndpoints) { - if ("/".equals(endpoint.getRootPath())) { - throw new IllegalStateException( - "Management endpoints and endpoint path are both mapped to '/' on the server port which will " - + "block access to other endpoints. Please use a different path for management endpoints or " - + "map them to a dedicated management port."); - } - } - } - boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java index 24b8de40e4e..760df4cc8f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.autoconfigure.web.server; import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -45,7 +44,7 @@ import org.springframework.util.Assert; */ @AutoConfiguration @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) -@EnableConfigurationProperties({ WebEndpointProperties.class, ManagementServerProperties.class }) +@EnableConfigurationProperties(ManagementServerProperties.class) public class ManagementContextAutoConfiguration { @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java index 5850e0318dc..bc7422b012b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,8 @@ class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint imp } private Stream getAdditionalPaths(WebServerNamespace webServerNamespace, AdditionalPathsMapper mapper) { - return mapper.getAdditionalPaths(getEndpointId(), webServerNamespace).stream(); + List additionalPaths = mapper.getAdditionalPaths(getEndpointId(), webServerNamespace); + return (additionalPaths != null) ? additionalPaths.stream() : Stream.empty(); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java deleted file mode 100644 index 8cfa7ce64bc..00000000000 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package smoketest.actuator; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.boot.SpringApplication; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Verifies that an exception is thrown when management and server endpoint paths - * conflict. - * - * @author Yongjun Hong - */ -class ManagementEndpointConflictSmokeTest { - - @Test - void shouldThrowExceptionWhenManagementAndServerPathsConflict() { - assertThatThrownBy(() -> { - SpringApplication.run(SampleActuatorApplication.class, "--management.endpoints.web.base-path=/", - "--management.endpoints.web.path-mapping.health=/"); - }).isInstanceOf(BeanCreationException.class) - .hasMessageContaining("Management endpoints and endpoint path are both mapped to '/'"); - } - -} \ No newline at end of file diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java new file mode 100644 index 00000000000..8ac09309e6b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.actuator; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.SpringApplication; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Verifies that an exception is thrown when management and server endpoint paths + * conflict. + * + * @author Yongjun Hong + */ +class ManagementEndpointConflictSmokeTests { + + @Test + void shouldThrowExceptionWhenManagementAndServerPathsConflict() { + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> SpringApplication.run(SampleActuatorApplication.class, + "--management.endpoints.web.base-path=/", "--management.endpoints.web.path-mapping.health=/")) + .withMessageContaining("Management base path and the 'health' actuator endpoint are both mapped to '/'"); + } + +}