This is an automated email from the ASF dual-hosted git repository.
SbloodyS pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git
The following commit(s) were added to refs/heads/dev by this push:
new 511823be90 [Fix-18582][Authentication] Align actuator endpoint
matching (#18583)
511823be90 is described below
commit 511823be90edb050c256d09a49df12972fde10e4
Author: Wenjun Ruan <[email protected]>
AuthorDate: Tue Aug 25 09:32:14 2026 +0800
[Fix-18582][Authentication] Align actuator endpoint matching (#18583)
---
.../pom.xml | 17 +++
.../ActuatorAuthenticationAutoConfiguration.java | 15 +--
...ctuatorAuthenticationAutoConfigurationTest.java | 128 +++++++++++++++++++++
3 files changed, 149 insertions(+), 11 deletions(-)
diff --git
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
index c1751855ee..799a0182e3 100644
---
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
+++
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/pom.xml
@@ -38,10 +38,27 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+ </dependency>
+
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.dolphinscheduler</groupId>
+ <artifactId>dolphinscheduler-meter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
</project>
diff --git
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
index bf70c56d04..9f95d84a19 100644
---
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
+++
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/main/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfiguration.java
@@ -26,6 +26,7 @@ import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
+import
org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import
org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -37,7 +38,6 @@ import
org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* Security configuration for Actuator endpoints.
@@ -57,8 +57,6 @@ import
org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Slf4j
public class ActuatorAuthenticationAutoConfiguration {
- private static final String ACTUATOR_PATH_PATTERN_1 =
"/dolphinscheduler/actuator/";
- private static final String ACTUATOR_PATH_PATTERN_2 = "/actuator/";
private static final String ROLE_ACTUATOR = "ACTUATOR";
@Bean
@@ -68,9 +66,8 @@ public class ActuatorAuthenticationAutoConfiguration {
log.info(
"Initialize ActuatorSecurityConfiguration,
management.security.enabled: {}, management.security.exclude: {}",
properties.isEnabled(), properties.getExclude());
- // Restrict this security configuration to requests starting with
actuator paths
- http.requestMatcher(request ->
request.getRequestURI().startsWith(ACTUATOR_PATH_PATTERN_1) ||
- request.getRequestURI().startsWith(ACTUATOR_PATH_PATTERN_2));
+ // Restrict this security configuration to registered actuator
endpoints
+ http.requestMatcher(EndpointRequest.toAnyEndpoint());
if (properties.isEnabled()) {
http.authorizeHttpRequests(authz -> {
@@ -78,11 +75,7 @@ public class ActuatorAuthenticationAutoConfiguration {
for (String endpoint : properties.getExclude()) {
if (StringUtils.isNotBlank(endpoint)) {
String cleanEndpoint = endpoint.trim();
- // Match both standard and prefixed actuator paths
- authz.requestMatchers(
- new
AntPathRequestMatcher(ACTUATOR_PATH_PATTERN_2 + cleanEndpoint)).permitAll();
- authz.requestMatchers(
- new
AntPathRequestMatcher(ACTUATOR_PATH_PATTERN_1 + cleanEndpoint)).permitAll();
+
authz.requestMatchers(EndpointRequest.to(cleanEndpoint)).permitAll();
}
}
// All other actuator requests require the ACTUATOR role
diff --git
a/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
new file mode 100644
index 0000000000..6362407f24
--- /dev/null
+++
b/dolphinscheduler-authentication/dolphinscheduler-actuator-authentication/src/test/java/org/apache/dolphinscheduler/authentication/actuator/config/ActuatorAuthenticationAutoConfigurationTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.dolphinscheduler.authentication.actuator.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URI;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+class ActuatorAuthenticationAutoConfigurationTest {
+
+ private static final String CONTEXT_PATH = "/dolphinscheduler";
+ private static final String USERNAME = "actuator-user";
+ private static final String PASSWORD = "actuator-password";
+
+ private final TestRestTemplate restTemplate = new TestRestTemplate();
+
+ @Test
+ void shouldAuthenticateEquivalentActuatorPaths() {
+ try (ConfigurableApplicationContext context = startApplication(true)) {
+ assertThat(get(context, "/actuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.UNAUTHORIZED);
+ assertThat(get(context, "/%61ctuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.UNAUTHORIZED);
+ assertThat(get(context, "/%61ctuator/prometheus", USERNAME,
"wrong-password").getStatusCode())
+ .isEqualTo(HttpStatus.UNAUTHORIZED);
+ assertThat(get(context, "/%61ctuator/prometheus", USERNAME,
PASSWORD).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ assertThat(get(context, "/actuator/health", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ assertThat(get(context, "/actuator/%68ealth", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ }
+ }
+
+ @Test
+ void shouldAuthenticateActuatorPathsWithoutContextPath() {
+ try (ConfigurableApplicationContext context = startApplication(true,
"")) {
+ assertThat(get(context, "/actuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.UNAUTHORIZED);
+ assertThat(get(context, "/%61ctuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.UNAUTHORIZED);
+ assertThat(get(context, "/%61ctuator/prometheus", USERNAME,
PASSWORD).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ }
+ }
+
+ @Test
+ void shouldPermitActuatorEndpointsWhenAuthenticationIsDisabled() {
+ try (ConfigurableApplicationContext context = startApplication(false))
{
+ assertThat(get(context, "/actuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ assertThat(get(context, "/%61ctuator/prometheus", null,
null).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ }
+ }
+
+ private ConfigurableApplicationContext startApplication(boolean
securityEnabled) {
+ return startApplication(securityEnabled, CONTEXT_PATH);
+ }
+
+ private ConfigurableApplicationContext startApplication(boolean
securityEnabled, String contextPath) {
+ SpringApplication application = new
SpringApplicationBuilder(TestApplication.class)
+ .web(WebApplicationType.SERVLET)
+ .properties(
+ "server.port=0",
+ "server.servlet.context-path=" + contextPath,
+ "management.security.enabled=" + securityEnabled,
+ "management.security.username=" + USERNAME,
+ "management.security.password=" + PASSWORD,
+ "management.security.exclude=health,metrics",
+
"management.endpoints.web.exposure.include=health,metrics,prometheus",
+ "spring.main.banner-mode=off")
+ .build();
+ return application.run();
+ }
+
+ private ResponseEntity<String> get(
+ ConfigurableApplicationContext context,
+ String path,
+ String username,
+ String password) {
+ ServletWebServerApplicationContext webServerApplicationContext =
+ (ServletWebServerApplicationContext) context;
+ int port = webServerApplicationContext.getWebServer().getPort();
+ String contextPath =
webServerApplicationContext.getServletContext().getContextPath();
+ URI uri = URI.create("http://127.0.0.1:" + port + contextPath + path);
+ HttpHeaders headers = new HttpHeaders();
+ if (username != null) {
+ headers.setBasicAuth(username, password);
+ }
+ return restTemplate.exchange(uri, HttpMethod.GET, new
HttpEntity<>(headers), String.class);
+ }
+
+ @SpringBootConfiguration
+ @EnableAutoConfiguration
+ static class TestApplication {
+ }
+}