This is an automated email from the ASF dual-hosted git repository. CalvinKirs pushed a commit to branch 4.0-65042-59708 in repository https://gitbox.apache.org/repos/asf/doris.git
commit ab91660eea3021976d1a2f6ab9bea72726135d3b 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 7e240789345..78f79057d35 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 @@ -613,6 +613,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); @@ -660,6 +662,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); @@ -692,6 +696,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 7af8a05b648..884c4d95cf6 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 @@ -372,7 +372,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]
