This is an automated email from the ASF dual-hosted git repository.

CalvinKirs pushed a commit to branch 4.1-65042-59708
in repository https://gitbox.apache.org/repos/asf/doris.git

commit 526101dabb1ae39c1d9350abae76485fbc04399d
Author: guoqiang <[email protected]>
AuthorDate: Tue Jun 30 18:28:58 2026 +0800

    [fix](auth) add authentication and authorization for manager node and query 
qerror REST APIs
    
    The node management endpoints (POST 
/rest/v2/manager/node/{action}/{fe,be,broker})
    allowed adding or dropping cluster nodes without any authentication or
    authorization. Add executeCheckPassword + checkAdminAuth so they require an
    authenticated ADMIN user, consistent with set_config/fe and set_config/be.
    
    GET /rest/v2/manager/query/qerror/{id} (getStats) had neither authentication
    nor authorization: its signature took no request/response and the global
    AuthInterceptor only covers /rest/v1/**, so it was reachable anonymously 
even
    with enable_all_http_auth=true. Add executeCheckPassword and
    checkAuthByUserAndQueryId, matching the /profile and /trace_id endpoints, 
so a
    non-admin can only read their own query stats.
    
    Add a p0 regression test covering both gaps.
---
 .../doris/httpv2/rest/manager/NodeAction.java      |   6 ++
 .../httpv2/rest/manager/QueryProfileAction.java    |  10 +-
 .../auth_p0/test_http_node_action_auth.groovy      | 102 +++++++++++++++++++++
 3 files changed, 117 insertions(+), 1 deletion(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/NodeAction.java 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/NodeAction.java
index 04a3eb2a1c2..1ad24aaf3d6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/NodeAction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/NodeAction.java
@@ -614,6 +614,8 @@ public class NodeAction extends RestBaseController {
     @PostMapping("/{action}/be")
     public Object operateBackend(HttpServletRequest request, 
HttpServletResponse response,
             @PathVariable("action") String action, @RequestBody BackendReqInfo 
reqInfo) {
+        ActionAuthorizationInfo authInfo = executeCheckPassword(request, 
response);
+        checkAdminAuth(authInfo.userIdentity);
         try {
             if (needRedirect(request.getScheme())) {
                 return redirectToHttps(request);
@@ -661,6 +663,8 @@ public class NodeAction extends RestBaseController {
     @PostMapping("/{action}/fe")
     public Object operateFrontends(HttpServletRequest request, 
HttpServletResponse response,
             @PathVariable("action") String action, @RequestBody 
FrontendReqInfo reqInfo) {
+        ActionAuthorizationInfo authInfo = executeCheckPassword(request, 
response);
+        checkAdminAuth(authInfo.userIdentity);
         try {
             if (needRedirect(request.getScheme())) {
                 return redirectToHttps(request);
@@ -693,6 +697,8 @@ public class NodeAction extends RestBaseController {
     @PostMapping("/{action}/broker")
     public Object operateBroker(HttpServletRequest request, 
HttpServletResponse response,
                                 @PathVariable("action") String action, 
@RequestBody BrokerReqInfo reqInfo) {
+        ActionAuthorizationInfo authInfo = executeCheckPassword(request, 
response);
+        checkAdminAuth(authInfo.userIdentity);
         try {
             if (!Env.getCurrentEnv().isMaster()) {
                 return redirectToMasterOrException(request, response);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
index e619a81326e..27b955522ae 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
@@ -373,7 +373,15 @@ public class QueryProfileAction extends RestBaseController 
{
      *  Query qError.
      */
     @RequestMapping(path = "/qerror/{id}", method = RequestMethod.GET)
-    public ResponseEntity<String> getStats(@PathVariable(value = "id") String 
id) {
+    public Object getStats(HttpServletRequest request, HttpServletResponse 
response,
+            @PathVariable(value = "id") String id) {
+        executeCheckPassword(request, response);
+        try {
+            checkAuthByUserAndQueryId(id);
+        } catch (AuthenticationException e) {
+            return ResponseEntityBuilder.badRequest(e.getMessage());
+        }
+
         ProfileElement profile = 
ProfileManager.getInstance().findProfileElementObject(id);
         if (profile == null) {
             return ResponseEntityBuilder.notFound(null);
diff --git a/regression-test/suites/auth_p0/test_http_node_action_auth.groovy 
b/regression-test/suites/auth_p0/test_http_node_action_auth.groovy
new file mode 100644
index 00000000000..6ddc95ffe89
--- /dev/null
+++ b/regression-test/suites/auth_p0/test_http_node_action_auth.groovy
@@ -0,0 +1,102 @@
+// 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.
+
+import org.junit.Assert;
+
+// Verify the node management endpoints (add/drop fe/be/broker) require
+// authentication and ADMIN privilege. Without the check, any caller could
+// add or drop cluster nodes via these REST APIs.
+suite("test_http_node_action_auth", "p0,auth,nonConcurrent") {
+    String suiteName = "test_http_node_action_auth"
+    String user = "${suiteName}_user"
+    String pwd = 'C123_567p'
+    try_sql("DROP USER ${user}")
+    sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
+
+    try {
+        sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = 
"true"); """
+
+        def operateFe = { check_func ->
+            httpTest {
+                basicAuthorization "${user}", "${pwd}"
+                endpoint "${context.config.feHttpAddress}"
+                uri "/rest/v2/manager/node/ADD/fe"
+                op "post"
+                body """{"role": "OBSERVER", "hostPort": "127.0.0.1:9010"}"""
+                check check_func
+            }
+        }
+
+        def operateBe = { check_func ->
+            httpTest {
+                basicAuthorization "${user}", "${pwd}"
+                endpoint "${context.config.feHttpAddress}"
+                uri "/rest/v2/manager/node/ADD/be"
+                op "post"
+                body """{"hostPorts": ["127.0.0.1:9050"]}"""
+                check check_func
+            }
+        }
+
+        // A non-admin user must be rejected by the ADMIN privilege check.
+        operateFe.call() {
+            respCode, body ->
+                log.info("add fe (non-admin) body:${body}")
+                assertTrue("${body}".contains("Unauthorized"))
+        }
+
+        operateBe.call() {
+            respCode, body ->
+                log.info("add be (non-admin) body:${body}")
+                assertTrue("${body}".contains("Unauthorized"))
+        }
+
+        sql """grant 'admin' to ${user}"""
+
+        // After granting ADMIN, the request passes the auth check. The add
+        // operation itself may still fail (fake host), but it must no longer
+        // be rejected with an authorization error.
+        operateFe.call() {
+            respCode, body ->
+                log.info("add fe (admin) body:${body}")
+                assertFalse("${body}".contains("Unauthorized"))
+        }
+
+        operateBe.call() {
+            respCode, body ->
+                log.info("add be (admin) body:${body}")
+                assertFalse("${body}".contains("Unauthorized"))
+        }
+
+        // The query qerror endpoint must require authentication. Without
+        // credentials it must not return the stats payload (200 ok).
+        httpTest {
+            endpoint "${context.config.feHttpAddress}"
+            uri "/rest/v2/manager/query/qerror/no_such_query_id"
+            op "get"
+            check {
+                respCode, body ->
+                    log.info("qerror (no auth) respCode:${respCode} 
body:${body}")
+                    assertTrue(respCode == 401 || 
"${body}".contains("Unauthorized")
+                            || "${body}".contains("Authentication"))
+            }
+        }
+    } finally {
+        sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = 
"false"); """
+        try_sql("DROP USER ${user}")
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to