You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by lu...@apache.org on 2023/06/27 01:57:26 UTC

[doris] branch master updated: [feature-wip](workload-group) Support for workload group Authentication (#20242)

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

luozenglin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new efcc65a0d3 [feature-wip](workload-group) Support for workload group Authentication (#20242)
efcc65a0d3 is described below

commit efcc65a0d386d1291e4d05ba7b862cbfbbc4ccc4
Author: luozenglin <lu...@baidu.com>
AuthorDate: Tue Jun 27 09:57:18 2023 +0800

    [feature-wip](workload-group) Support for workload group Authentication (#20242)
---
 .../admin-manual/privilege-ldap/user-privilege.md  |  13 +
 docs/en/docs/admin-manual/workload-group.md        |   2 +
 .../Account-Management-Statements/GRANT.md         |  28 +-
 .../Show-Statements/SHOW-WORKLOAD-GROUPS.md        |   2 +-
 .../admin-manual/privilege-ldap/user-privilege.md  |   9 +-
 docs/zh-CN/docs/admin-manual/workload-group.md     |   2 +
 .../Account-Management-Statements/GRANT.md         |  32 ++-
 .../Show-Statements/SHOW-WORKLOAD-GROUPS.md        |   2 +-
 fe/fe-core/src/main/cup/sql_parser.cup             |  28 ++
 .../java/org/apache/doris/analysis/GrantStmt.java  |  71 ++++--
 .../java/org/apache/doris/analysis/RevokeStmt.java |  44 ++--
 .../doris/analysis/WorkloadGroupPattern.java       |  93 +++++++
 .../org/apache/doris/common/CaseSensibility.java   |   3 +-
 .../org/apache/doris/common/proc/AuthProcDir.java  |   2 +-
 .../org/apache/doris/ldap/LdapPrivsChecker.java    |  15 ++
 .../mysql/privilege/AccessControllerManager.java   |   8 +
 .../org/apache/doris/mysql/privilege/Auth.java     | 109 +++++++-
 .../doris/mysql/privilege/PrivPredicate.java       |   5 +-
 .../apache/doris/mysql/privilege/Privilege.java    |  13 +-
 .../org/apache/doris/mysql/privilege/Role.java     | 131 +++++++++-
 .../apache/doris/mysql/privilege/RoleManager.java  |  36 ++-
 .../mysql/privilege/SystemAccessController.java    |   4 +
 .../mysql/privilege/WorkloadGroupPrivEntry.java    |  98 +++++++
 .../privilege/WorkloadGroupPrivTable.java}         |  43 ++--
 .../java/org/apache/doris/persist/PrivInfo.java    |  20 ++
 .../resource/workloadgroup/WorkloadGroup.java      |   2 +-
 .../resource/workloadgroup/WorkloadGroupMgr.java   |  21 +-
 .../org/apache/doris/mysql/privilege/AuthTest.java | 282 +++++++++++++++++++++
 .../workloadgroup/WorkloadGroupMgrTest.java        |  13 +
 29 files changed, 1037 insertions(+), 94 deletions(-)

diff --git a/docs/en/docs/admin-manual/privilege-ldap/user-privilege.md b/docs/en/docs/admin-manual/privilege-ldap/user-privilege.md
index a034c3870f..5159a76a9c 100644
--- a/docs/en/docs/admin-manual/privilege-ldap/user-privilege.md
+++ b/docs/en/docs/admin-manual/privilege-ldap/user-privilege.md
@@ -149,6 +149,10 @@ Doris currently supports the following permissions
 
 	Delete permissions for databases, tables, and views.
 
+8. Usage_priv
+
+   Use of resources <version since="dev" type="inline" >and workload groups</version>.
+
 ## Permission hierarchy
 
 At the same time, according to the scope of application of permissions, we divide them into four levels:
@@ -158,6 +162,15 @@ At the same time, according to the scope of application of permissions, we divid
 3. DATABASE LEVEL: Database-level permissions. That is, the permissions on `ctl.db.*` granted through the GRANT statement. The privileges granted apply to any table in the specified database.
 4. TABLE LEVEL: Table-level permissions. That is, the permissions on `ctl.db.tbl` granted through the GRANT statement. The privileges granted apply to the specified table in the specified database.
 
+The privileges of the resources are divided into two levels as follows:
+
+1. GLOBAL LEVEL: Global privileges. That is, the privileges granted on `*` by the GRANT statement. The privileges granted apply to the resource.
+2. RESOURCE LEVEL: Resource-level privileges. This is the permission on `resource_name` granted by the GRANT statement. The privilege granted applies to the specified resource.
+
+<version since="dev">
+The workload group has only one level:
+1. WORKLOAD GROUP LEVEL: privileges on `workload_group_name` that can be granted with the GRANT statement. The privileges granted apply to the specified workload group. workload_group_name supports `%` and `_` match characters, `%` can match any string and `_` matches any single character.
+</version>
 
 ## ADMIN /GRANT
 
diff --git a/docs/en/docs/admin-manual/workload-group.md b/docs/en/docs/admin-manual/workload-group.md
index 0b1e366121..242bb9dd13 100644
--- a/docs/en/docs/admin-manual/workload-group.md
+++ b/docs/en/docs/admin-manual/workload-group.md
@@ -75,6 +75,8 @@ set workload_group = 'g2'.
 ```
 session variable `workload_group` takes precedence over user property `default_workload_group`, in case `workload_group` is empty, the query will be bound to `default_workload_group`, in case session variable ` workload_group` is not empty, the query will be bound to `workload_group`.
 
+If you are a non-admin user, you need to execute [SHOW-WORKLOAD-GROUPS](../sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md) to check if the current user can see the workload group, if not, the workload group may not exist or the current user does not have permission to execute the query. If you cannot see the workload group, the workload group may not exist or the current user does not have privileges. To authorize the workload group, refer to: [grant statement](../sql-m [...]
+
 5. Execute the query, which will be associated with the g1 workload group.
 
 ### Query queue Function
diff --git a/docs/en/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md b/docs/en/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
index a4751cfb5b..4e29c85d2c 100644
--- a/docs/en/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
+++ b/docs/en/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
@@ -49,6 +49,8 @@ GRANT privilege_list ON RESOURCE resource_name TO user_identity [ROLE role_name]
 GRANT role_list TO user_identity
 ````
 
+<version since="dev">GRANT privilege_list ON WORKLOAD GROUP workload_group_name TO user_identity [ROLE role_name]</version>
+
 privilege_list is a list of privileges to be granted, separated by commas. Currently Doris supports the following permissions:
 
     NODE_PRIV: Cluster node operation permissions, including node online and offline operations. Only the root user has this permission and cannot be granted to other users.
@@ -68,7 +70,7 @@ Permission classification:
 
     1. Node Privilege: NODE_PRIV
     2. database table permissions: SELECT_PRIV, LOAD_PRIV, ALTER_PRIV, CREATE_PRIV, DROP_PRIV
-    3. Resource permission: USAGE_PRIV
+    3. Resource  <version since="dev" type="inline" >and workload groups</version> Privilege: USAGE_PRIV
 
 Priv_level supports the following four forms:
 
@@ -86,6 +88,8 @@ resource_name supports the following two forms:
     
     The resource specified here can be a non-existing resource. In addition, please distinguish the resources here from external tables, and use catalog as an alternative if you use external tables.
 
+workload_group_name specifies the workload group name and supports `%` and `_` match characters, `%` can match any string and `_` matches any single character.
+
 user_identity:
 
     The user_identity syntax here is the same as CREATE USER. And must be a user_identity created with CREATE USER. The host in user_identity can be a domain name. If it is a domain name, the effective time of the authority may be delayed by about 1 minute.
@@ -138,7 +142,27 @@ role_list is the list of roles to be assigned, separated by commas,the specified
 
     ```sql
     GRANT 'role1','role2' TO 'jack'@'%';
-    ```
+    ````
+
+<version since="dev"></version>
+
+8. Grant the specified workload group 'g1' to user jack
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO 'jack'@'%'.
+    ````
+
+9. match all workload groups granted to user jack
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP '%' TO 'jack'@'%'.
+    ````
+
+10. grant the workload group 'g1' to the role my_role
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO ROLE 'my_role'.
+    ````
 
 ### Keywords
 
diff --git a/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md b/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
index e892b31a57..706e9c7b63 100644
--- a/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
+++ b/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
@@ -34,7 +34,7 @@ SHOW workload GROUPS
 
 ### Description
 
-This statement is used to display all workload groups.
+This statement is used to display the resource groups for which the current user has usage_priv privileges.
 
 grammar:
 
diff --git a/docs/zh-CN/docs/admin-manual/privilege-ldap/user-privilege.md b/docs/zh-CN/docs/admin-manual/privilege-ldap/user-privilege.md
index 14277dac9d..692d7e68db 100644
--- a/docs/zh-CN/docs/admin-manual/privilege-ldap/user-privilege.md
+++ b/docs/zh-CN/docs/admin-manual/privilege-ldap/user-privilege.md
@@ -149,7 +149,7 @@ Doris 目前支持以下几种权限
 
 8. Usage_priv
 
-   资源的使用权限。
+   资源的使用权限<version since="dev" type="inline" >和workload group权限</version>。
 
 ## 权限层级
 
@@ -163,7 +163,12 @@ Doris 目前支持以下几种权限
 将资源的权限分为以下两个层级:
 
 1. GLOBAL LEVEL:全局权限。即通过 GRANT 语句授予的 `*` 上的权限。被授予的权限适用于资源。
-2. RESOURCE LEVEL: 资源级权限。即通过 GRANT 语句授予的 `resource_name` 上的权限。被授予的权限适用于指定资源。
+2. RESOURCE LEVEL: 资源级权限。即通过 GRANT 语句授予的 `resource_name` 上的权限。被授予的权限适用于指定资源。
+
+<version since="dev">
+workload group 只有一个层级:
+1. WORKLOAD GROUP LEVEL:可以通过 GRANT 语句授予 `workload_group_name` 上的权限。被授予的权限适用于指定workload group。workload_group_name 支持 `%`和`_`匹配符,`%`可匹配任意字符串,`_`匹配任意单个字符。
+</version>
 
 ## ADMIN/GRANT 权限说明
 
diff --git a/docs/zh-CN/docs/admin-manual/workload-group.md b/docs/zh-CN/docs/admin-manual/workload-group.md
index 816b90de10..775b10c199 100644
--- a/docs/zh-CN/docs/admin-manual/workload-group.md
+++ b/docs/zh-CN/docs/admin-manual/workload-group.md
@@ -74,6 +74,8 @@ set workload_group = 'g2';
 ```
 session变量`workload_group`优先于 user property `default_workload_group`, 在`workload_group`为空时,查询将绑定到`default_workload_group`, 在session变量`workload_group`不为空时,查询将绑定到`workload_group`。
 
+如果是非admin用户,需要先执行[SHOW-WORKLOAD-GROUPS](../sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md) 确认下当前用户能否看到该workload group,不能看到的workload group可能不存在或者当前用户没有权限,执行查询时会报错。给worklaod group授权参考:[grant语句](../sql-manual/sql-reference/Account-Management-Statements/GRANT.md)。
+
 5. 执行查询,查询将关联到指定的 workload group。
 
 ### 查询排队功能
diff --git a/docs/zh-CN/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md b/docs/zh-CN/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
index d5509f70a8..30839d0531 100644
--- a/docs/zh-CN/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
+++ b/docs/zh-CN/docs/sql-manual/sql-reference/Account-Management-Statements/GRANT.md
@@ -49,6 +49,8 @@ GRANT privilege_list ON RESOURCE resource_name TO user_identity [ROLE role_name]
 GRANT role_list TO user_identity
 ```
 
+<version since="dev">GRANT privilege_list ON WORKLOAD GROUP workload_group_name TO user_identity [ROLE role_name]</version>
+
 privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris 支持如下权限:
 
     NODE_PRIV:集群节点操作权限,包括节点上下线等操作,只有 root 用户有该权限,不可赋予其他用户。
@@ -59,7 +61,7 @@ privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris 
     ALTER_PRIV:对指定的库或表的schema变更权限
     CREATE_PRIV:对指定的库或表的创建权限
     DROP_PRIV:对指定的库或表的删除权限
-    USAGE_PRIV: 对指定资源的使用权限
+    USAGE_PRIV: 对指定资源的使用权限<version since="dev" type="inline" >和workload group权限</version>
     
     旧版权限中的 ALL 和 READ_WRITE 会被转换成:SELECT_PRIV,LOAD_PRIV,ALTER_PRIV,CREATE_PRIV,DROP_PRIV;
     READ_ONLY 会被转换为 SELECT_PRIV。
@@ -68,7 +70,7 @@ privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris 
 
     1. 节点权限:NODE_PRIV
     2. 库表权限:SELECT_PRIV,LOAD_PRIV,ALTER_PRIV,CREATE_PRIV,DROP_PRIV
-    3. 资源权限:USAGE_PRIV
+    3. 资源权限<version since="dev" type="inline" >和workload group权限</version>:USAGE_PRIV
 
 priv_level 支持以下四种形式:
 
@@ -86,6 +88,8 @@ resource_name 支持以下两种形式:
     
     这里指定的资源可以是不存在的资源。另外,这里的资源请跟外部表区分开,有使用外部表的情况请都使用catalog作为替代。
 
+workload_group_name 可指定workload group 名,支持 `%`和`_`匹配符,`%`可匹配任意字符串,`_`匹配任意单个字符。
+
 user_identity:
 
     这里的 user_identity 语法同 CREATE USER。且必须为使用 CREATE USER 创建过的 user_identity。user_identity 中的host可以是域名,如果是域名的话,权限的生效时间可能会有1分钟左右的延迟。
@@ -133,12 +137,32 @@ role_list 是需要赋予的角色列表,以逗号分隔,指定的角色必
     ```
    
 <version since="2.0.0"></version>
-
 7. 将指定角色授予某用户
 
     ```sql
     GRANT 'role1','role2' TO 'jack'@'%';
-    ```
+    ````
+
+
+<version since="dev"></version>
+
+8. 将指定 workload group ‘g1’授予用户jack
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO 'jack'@'%';
+    ````
+
+9. 匹配所有workload group 授予用户jack
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP '%' TO 'jack'@'%';
+    ````
+
+10. 将指定 workload group ‘g1’授予角色my_role
+
+    ```sql
+    GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO ROLE 'my_role';
+    ````
 
 ### Keywords
 
diff --git a/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md b/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
index 36d0640553..e6459c27b2 100644
--- a/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
+++ b/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-WORKLOAD-GROUPS.md
@@ -34,7 +34,7 @@ SHOW WORKLOAD GROUPS
 
 ### Description
 
-该语句用于展示所有资源组。
+该语句用于展示当前用户具有usage_priv权限的资源组。
 
 语法:
 
diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 500a83bb3c..597bb70696 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -856,6 +856,7 @@ nonterminal Separator opt_field_term, opt_line_term, separator;
 nonterminal String opt_user_role;
 nonterminal TablePattern tbl_pattern;
 nonterminal ResourcePattern resource_pattern;
+nonterminal WorkloadGroupPattern workload_group_pattern;
 nonterminal String ident_or_star;
 nonterminal Integer opt_skip_lines;
 
@@ -2864,6 +2865,14 @@ grant_stmt ::=
     {:
         RESULT = new GrantStmt(null, role, resourcePattern, privs);
     :}
+    | KW_GRANT privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_TO user_identity:userId
+    {:
+        RESULT = new GrantStmt(userId, null, workloadGroupPattern, privs);
+    :}
+    | KW_GRANT privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_TO KW_ROLE STRING_LITERAL:role
+    {:
+        RESULT = new GrantStmt(null, role, workloadGroupPattern, privs);
+    :}
     | KW_GRANT string_list:roles KW_TO user_identity:userId
     {:
         RESULT = new GrantStmt(roles, userId);
@@ -2896,6 +2905,17 @@ resource_pattern ::=
     :}
     ;
 
+workload_group_pattern ::=
+    ident_or_star:workloadGroupName
+    {:
+        RESULT = new WorkloadGroupPattern(workloadGroupName);
+    :}
+    | STRING_LITERAL:workloadGroupName
+    {:
+        RESULT = new WorkloadGroupPattern(workloadGroupName);
+    :}
+    ;
+
 ident_or_star ::=
     STAR
     {:
@@ -2925,6 +2945,14 @@ revoke_stmt ::=
     {:
         RESULT = new RevokeStmt(null, role, resourcePattern, privs);
     :}
+    | KW_REVOKE privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_FROM user_identity:userId
+    {:
+        RESULT = new RevokeStmt(userId, null, workloadGroupPattern, privs);
+    :}
+    | KW_REVOKE privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_FROM KW_ROLE STRING_LITERAL:role
+    {:
+        RESULT = new RevokeStmt(null, role, workloadGroupPattern, privs);
+    :}
     | KW_REVOKE string_list:roles KW_FROM user_identity:userId
     {:
         RESULT = new RevokeStmt(roles, userId);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
index 482656e93c..7608218397 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
@@ -47,28 +47,37 @@ public class GrantStmt extends DdlStmt {
     private String role;
     private TablePattern tblPattern;
     private ResourcePattern resourcePattern;
+    private WorkloadGroupPattern workloadGroupPattern;
     private List<Privilege> privileges;
     // Indicates that these roles are granted to a user
     private List<String> roles;
 
     public GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List<AccessPrivilege> privileges) {
-        this.userIdent = userIdent;
-        this.role = role;
-        this.tblPattern = tblPattern;
-        this.resourcePattern = null;
-        PrivBitSet privs = PrivBitSet.of();
-        for (AccessPrivilege accessPrivilege : privileges) {
-            privs.or(accessPrivilege.toPaloPrivilege());
-        }
-        this.privileges = privs.toPrivilegeList();
+        this(userIdent, role, tblPattern, null, null, privileges);
     }
 
     public GrantStmt(UserIdentity userIdent, String role,
             ResourcePattern resourcePattern, List<AccessPrivilege> privileges) {
+        this(userIdent, role, null, resourcePattern, null, privileges);
+    }
+
+    public GrantStmt(UserIdentity userIdent, String role,
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) {
+        this(userIdent, role, null, null, workloadGroupPattern, privileges);
+    }
+
+    public GrantStmt(List<String> roles, UserIdentity userIdent) {
+        this.userIdent = userIdent;
+        this.roles = roles;
+    }
+
+    private GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern,
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) {
         this.userIdent = userIdent;
         this.role = role;
-        this.tblPattern = null;
+        this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
+        this.workloadGroupPattern = workloadGroupPattern;
         PrivBitSet privs = PrivBitSet.of();
         for (AccessPrivilege accessPrivilege : privileges) {
             privs.or(accessPrivilege.toPaloPrivilege());
@@ -76,11 +85,6 @@ public class GrantStmt extends DdlStmt {
         this.privileges = privs.toPrivilegeList();
     }
 
-    public GrantStmt(List<String> roles, UserIdentity userIdent) {
-        this.userIdent = userIdent;
-        this.roles = roles;
-    }
-
     public UserIdentity getUserIdent() {
         return userIdent;
     }
@@ -93,6 +97,10 @@ public class GrantStmt extends DdlStmt {
         return resourcePattern;
     }
 
+    public WorkloadGroupPattern getWorkloadGroupPattern() {
+        return workloadGroupPattern;
+    }
+
     public boolean hasRole() {
         return !Strings.isNullOrEmpty(role);
     }
@@ -123,6 +131,8 @@ public class GrantStmt extends DdlStmt {
             tblPattern.analyze(analyzer);
         } else if (resourcePattern != null) {
             resourcePattern.analyze();
+        } else if (workloadGroupPattern != null) {
+            workloadGroupPattern.analyze();
         } else if (roles != null) {
             for (int i = 0; i < roles.size(); i++) {
                 String originalRoleName = roles.get(i);
@@ -139,6 +149,8 @@ public class GrantStmt extends DdlStmt {
             checkTablePrivileges(privileges, role, tblPattern);
         } else if (resourcePattern != null) {
             checkResourcePrivileges(privileges, role, resourcePattern);
+        } else if (workloadGroupPattern != null) {
+            checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
         } else if (roles != null) {
             checkRolePrivileges();
         }
@@ -153,6 +165,7 @@ public class GrantStmt extends DdlStmt {
      * 5.1 User should has GLOBAL level GRANT_PRIV
      * 5.2 or user has DATABASE/TABLE level GRANT_PRIV if grant/revoke to/from certain database or table.
      * 5.3 or user should has 'resource' GRANT_PRIV if grant/revoke to/from certain 'resource'
+     * 5.4 or user should has 'workload group' GRANT_PRIV if grant/revoke to/from certain 'workload group'
      * 6. Can not grant USAGE_PRIV to database or table
      *
      * @param privileges
@@ -216,8 +229,9 @@ public class GrantStmt extends DdlStmt {
             ResourcePattern resourcePattern) throws AnalysisException {
         for (int i = 0; i < Privilege.notBelongToResourcePrivileges.length; i++) {
             if (privileges.contains(Privilege.notBelongToResourcePrivileges[i])) {
-                throw new AnalysisException(String.format("Can not grant/revoke %s to/from any other users or roles",
-                        Privilege.notBelongToResourcePrivileges[i]));
+                throw new AnalysisException(
+                        String.format("Can not grant/revoke %s on resource to/from any other users or roles",
+                                Privilege.notBelongToResourcePrivileges[i]));
             }
         }
 
@@ -242,6 +256,27 @@ public class GrantStmt extends DdlStmt {
         }
     }
 
+    public static void checkWorkloadGroupPrivileges(List<Privilege> privileges, String role,
+            WorkloadGroupPattern workloadGroupPattern) throws AnalysisException {
+        for (int i = 0; i < Privilege.notBelongToWorkloadGroupPrivileges.length; i++) {
+            if (privileges.contains(Privilege.notBelongToWorkloadGroupPrivileges[i])) {
+                throw new AnalysisException(
+                        String.format("Can not grant/revoke %s on workload group to/from any other users or roles",
+                                Privilege.notBelongToWorkloadGroupPrivileges[i]));
+            }
+        }
+
+        if (role != null) {
+            // Rule 4
+            if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
+                ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
+            }
+        } else if (!Env.getCurrentEnv().getAccessManager().checkWorkloadGroupPriv(ConnectContext.get(),
+                workloadGroupPattern.getworkloadGroupName(), PrivPredicate.GRANT)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
+        }
+    }
+
     public static void checkRolePrivileges() throws AnalysisException {
         if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
             ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
@@ -262,6 +297,8 @@ public class GrantStmt extends DdlStmt {
             sb.append(" ON ").append(tblPattern).append(" TO ");
         } else if (resourcePattern != null) {
             sb.append(" ON RESOURCE '").append(resourcePattern).append("' TO ");
+        } else if (workloadGroupPattern != null) {
+            sb.append(" ON WORKLOAD GROUP '").append(workloadGroupPattern).append("' TO ");
         } else {
             sb.append(" TO ");
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
index 8dd7a1bafe..e008e9a8a8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
@@ -42,28 +42,37 @@ public class RevokeStmt extends DdlStmt {
     private String role;
     private TablePattern tblPattern;
     private ResourcePattern resourcePattern;
+    private WorkloadGroupPattern workloadGroupPattern;
     private List<Privilege> privileges;
     // Indicates that these roles are revoked from a user
     private List<String> roles;
 
     public RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List<AccessPrivilege> privileges) {
-        this.userIdent = userIdent;
-        this.role = role;
-        this.tblPattern = tblPattern;
-        this.resourcePattern = null;
-        PrivBitSet privs = PrivBitSet.of();
-        for (AccessPrivilege accessPrivilege : privileges) {
-            privs.or(accessPrivilege.toPaloPrivilege());
-        }
-        this.privileges = privs.toPrivilegeList();
+        this(userIdent, role, tblPattern, null, null, privileges);
     }
 
     public RevokeStmt(UserIdentity userIdent, String role,
             ResourcePattern resourcePattern, List<AccessPrivilege> privileges) {
+        this(userIdent, role, null, resourcePattern, null, privileges);
+    }
+
+    public RevokeStmt(UserIdentity userIdent, String role,
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) {
+        this(userIdent, role, null, null, workloadGroupPattern, privileges);
+    }
+
+    public RevokeStmt(List<String> roles, UserIdentity userIdent) {
+        this.roles = roles;
+        this.userIdent = userIdent;
+    }
+
+    private RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern,
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> privileges) {
         this.userIdent = userIdent;
         this.role = role;
-        this.tblPattern = null;
+        this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
+        this.workloadGroupPattern = workloadGroupPattern;
         PrivBitSet privs = PrivBitSet.of();
         for (AccessPrivilege accessPrivilege : privileges) {
             privs.or(accessPrivilege.toPaloPrivilege());
@@ -71,11 +80,6 @@ public class RevokeStmt extends DdlStmt {
         this.privileges = privs.toPrivilegeList();
     }
 
-    public RevokeStmt(List<String> roles, UserIdentity userIdent) {
-        this.roles = roles;
-        this.userIdent = userIdent;
-    }
-
     public UserIdentity getUserIdent() {
         return userIdent;
     }
@@ -88,6 +92,10 @@ public class RevokeStmt extends DdlStmt {
         return resourcePattern;
     }
 
+    public WorkloadGroupPattern getWorkloadGroupPattern() {
+        return workloadGroupPattern;
+    }
+
     public String getQualifiedRole() {
         return role;
     }
@@ -113,6 +121,8 @@ public class RevokeStmt extends DdlStmt {
             tblPattern.analyze(analyzer);
         } else if (resourcePattern != null) {
             resourcePattern.analyze();
+        } else if (workloadGroupPattern != null) {
+            workloadGroupPattern.analyze();
         } else if (roles != null) {
             for (int i = 0; i < roles.size(); i++) {
                 String originalRoleName = roles.get(i);
@@ -130,6 +140,8 @@ public class RevokeStmt extends DdlStmt {
             GrantStmt.checkTablePrivileges(privileges, role, tblPattern);
         } else if (resourcePattern != null) {
             GrantStmt.checkResourcePrivileges(privileges, role, resourcePattern);
+        } else if (workloadGroupPattern != null) {
+            GrantStmt.checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
         } else if (roles != null) {
             GrantStmt.checkRolePrivileges();
         }
@@ -149,6 +161,8 @@ public class RevokeStmt extends DdlStmt {
             sb.append(" ON ").append(tblPattern).append(" FROM ");
         } else if (resourcePattern != null) {
             sb.append(" ON RESOURCE '").append(resourcePattern).append("' FROM ");
+        } else if (workloadGroupPattern != null) {
+            sb.append(" ON WORKLOAD GROUP '").append(workloadGroupPattern).append("' FROM ");
         } else {
             sb.append(" FROM ");
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/WorkloadGroupPattern.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/WorkloadGroupPattern.java
new file mode 100644
index 0000000000..748f2da847
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/WorkloadGroupPattern.java
@@ -0,0 +1,93 @@
+// 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.doris.analysis;
+
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.Auth.PrivLevel;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.common.base.Strings;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class WorkloadGroupPattern implements Writable {
+
+    @SerializedName(value = "workloadGroupName")
+    private String workloadGroupName;
+
+    private WorkloadGroupPattern() {
+    }
+
+    public WorkloadGroupPattern(String workloadGroupName) {
+        this.workloadGroupName = workloadGroupName;
+    }
+
+    public String getworkloadGroupName() {
+        return workloadGroupName;
+    }
+
+    public PrivLevel getPrivLevel() {
+        return PrivLevel.WORKLOAD_GROUP;
+    }
+
+    public void analyze() throws AnalysisException {
+        if (Strings.isNullOrEmpty(workloadGroupName)) {
+            throw new AnalysisException("Workload group name is empty.");
+        }
+        if (workloadGroupName.equals("*")) {
+            throw new AnalysisException("Global workload group priv is not supported.");
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof WorkloadGroupPattern)) {
+            return false;
+        }
+        WorkloadGroupPattern other = (WorkloadGroupPattern) obj;
+        return workloadGroupName.equals(other.getworkloadGroupName());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + workloadGroupName.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return workloadGroupName;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        String json = GsonUtils.GSON.toJson(this);
+        Text.writeString(out, json);
+    }
+
+    public static WorkloadGroupPattern read(DataInput in) throws IOException {
+        String json = Text.readString(in);
+        return GsonUtils.GSON.fromJson(json, WorkloadGroupPattern.class);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java b/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java
index 6d5da6e65f..3417107a64 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java
@@ -35,7 +35,8 @@ public enum CaseSensibility {
     VARIABLES(true),
     RESOURCE(true),
     CONFIG(true),
-    ROUTINE_LOAD(true);
+    ROUTINE_LOAD(true),
+    WORKLOAD_GROUP(true);
 
     private boolean caseSensitive;
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
index dc33ae3f35..e4cc0c96ec 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
@@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList;
 public class AuthProcDir implements ProcDirInterface {
     public static final ImmutableList<String> TITLE_NAMES = new ImmutableList.Builder<String>()
             .add("UserIdentity").add("Password").add("Roles").add("GlobalPrivs").add("CatalogPrivs")
-            .add("DatabasePrivs").add("TablePrivs").add("ResourcePrivs").build();
+            .add("DatabasePrivs").add("TablePrivs").add("ResourcePrivs").add("WorkloadGroupPrivs").build();
 
     private Auth auth;
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java
index e176363bd7..e86d335864 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/ldap/LdapPrivsChecker.java
@@ -72,6 +72,12 @@ public class LdapPrivsChecker {
                 resourceName, wanted);
     }
 
+    public static boolean hasWorkloadGroupPrivFromLdap(UserIdentity currentUser, String workloadGroupName,
+            PrivPredicate wanted) {
+        return hasLdapPrivs(currentUser) && getUserLdapPrivs(currentUser.getQualifiedUser()).checkWorkloadGroupPriv(
+                workloadGroupName, wanted);
+    }
+
     public static PrivBitSet getResourcePrivFromLdap(UserIdentity currentUser, String resourceName) {
         PrivBitSet savedPrivs = PrivBitSet.of();
         if (hasLdapPrivs(currentUser)) {
@@ -80,6 +86,15 @@ public class LdapPrivsChecker {
         return savedPrivs;
     }
 
+    public static PrivBitSet getWorkloadGroupPrivFromLdap(UserIdentity currentUser, String workloadGroupName) {
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (hasLdapPrivs(currentUser)) {
+            getUserLdapPrivs(currentUser.getQualifiedUser()).getWorkloadGroupPrivTable()
+                    .getPrivs(workloadGroupName, savedPrivs);
+        }
+        return savedPrivs;
+    }
+
     public static boolean hasLdapPrivs(UserIdentity userIdent) {
         return LdapConfig.ldap_authentication_enabled && Env.getCurrentEnv().getAuth().getLdapManager()
                 .doesUserExist(userIdent.getQualifiedUser());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
index f68cda8232..3e0ff23915 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
@@ -214,6 +214,14 @@ public class AccessControllerManager {
         return sysAccessController.checkResourcePriv(currentUser, resourceName, wanted);
     }
 
+    public boolean checkWorkloadGroupPriv(ConnectContext ctx, String workloadGroupName, PrivPredicate wanted) {
+        return checkWorkloadGroupPriv(ctx.getCurrentUserIdentity(), workloadGroupName, wanted);
+    }
+
+    public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadGroupName, PrivPredicate wanted) {
+        return sysAccessController.checkWorkloadGroupPriv(currentUser, workloadGroupName, wanted);
+    }
+
     // ==== Other ====
     public boolean checkPrivByAuthInfo(ConnectContext ctx, AuthorizationInfo authInfo, PrivPredicate wanted) {
         if (authInfo == null) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
index 0a3441d15f..ea39bb9f08 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
@@ -33,6 +33,7 @@ import org.apache.doris.analysis.SetPassVar;
 import org.apache.doris.analysis.SetUserPropertyStmt;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.InfoSchemaDb;
 import org.apache.doris.cluster.ClusterNamespace;
@@ -125,7 +126,7 @@ public class Auth implements Writable {
     }
 
     public enum PrivLevel {
-        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE
+        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE, WORKLOAD_GROUP
     }
 
     public Auth() {
@@ -356,6 +357,26 @@ public class Auth implements Writable {
         }
     }
 
+    // ==== Workload Group ====
+    public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadGroupName, PrivPredicate wanted) {
+        if (isLdapAuthEnabled() && LdapPrivsChecker.hasWorkloadGroupPrivFromLdap(currentUser, workloadGroupName,
+                wanted)) {
+            return true;
+        }
+        readLock();
+        try {
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkWorkloadGroupPriv(workloadGroupName, wanted)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
+    }
+
     // ==== Other ====
     /*
      * Check if current user has certain privilege.
@@ -508,6 +529,10 @@ public class Auth implements Writable {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs,
                     true /* err on non exist */, false /* not replay */);
+        } else if (stmt.getWorkloadGroupPattern() != null) {
+            PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
+            grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getWorkloadGroupPattern(), privs,
+                    true /* err on non exist */, false /* not replay */);
         } else {
             grantInternal(stmt.getUserIdent(), stmt.getRoles(), false);
         }
@@ -523,6 +548,10 @@ public class Auth implements Writable {
                 grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
                         privInfo.getResourcePattern(), privInfo.getPrivs(),
                         true /* err on non exist */, true /* is replay */);
+            } else if (privInfo.getWorkloadGroupPattern() != null) {
+                grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
+                        privInfo.getWorkloadGroupPattern(), privInfo.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
             } else {
                 grantInternal(privInfo.getUserIdent(), privInfo.getRoles(), true);
             }
@@ -578,6 +607,27 @@ public class Auth implements Writable {
         }
     }
 
+    private void grantInternal(UserIdentity userIdent, String role, WorkloadGroupPattern workloadGroupPattern,
+            PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+
+            Role newRole = new Role(role, workloadGroupPattern, privs);
+            roleManager.addOrMergeRole(newRole, false /* err on exist */);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, workloadGroupPattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logGrantPriv(info);
+            }
+            LOG.info("finished to grant workload group privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
     // grant for roles
     private void grantInternal(UserIdentity userIdent, List<String> roles, boolean isReplay) throws DdlException {
         writeLock();
@@ -631,6 +681,10 @@ public class Auth implements Writable {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getResourcePattern(), privs,
                     true /* err on non exist */, false /* is replay */);
+        } else if (stmt.getWorkloadGroupPattern() != null) {
+            PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
+            revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getWorkloadGroupPattern(), privs,
+                    true /* err on non exist */, false /* is replay */);
         } else {
             revokeInternal(stmt.getUserIdent(), stmt.getRoles(), false);
         }
@@ -644,6 +698,9 @@ public class Auth implements Writable {
             } else if (info.getResourcePattern() != null) {
                 revokeInternal(info.getUserIdent(), info.getRole(), info.getResourcePattern(), info.getPrivs(),
                         true /* err on non exist */, true /* is replay */);
+            } else if (info.getWorkloadGroupPattern() != null) {
+                revokeInternal(info.getUserIdent(), info.getRole(), info.getWorkloadGroupPattern(), info.getPrivs(),
+                        true /* err on non exist */, true /* is replay */);
             } else {
                 revokeInternal(info.getUserIdent(), info.getRoles(), false);
             }
@@ -693,6 +750,27 @@ public class Auth implements Writable {
         }
     }
 
+    private void revokeInternal(UserIdentity userIdent, String role, WorkloadGroupPattern workloadGroupPattern,
+            PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException {
+        writeLock();
+        try {
+            if (role == null) {
+                role = roleManager.getUserDefaultRoleName(userIdent);
+            }
+
+            // revoke privs from role
+            roleManager.revokePrivs(role, workloadGroupPattern, privs, errOnNonExist);
+
+            if (!isReplay) {
+                PrivInfo info = new PrivInfo(userIdent, workloadGroupPattern, privs, null, role);
+                Env.getCurrentEnv().getEditLog().logRevokePriv(info);
+            }
+            LOG.info("finished to revoke privilege. is replay: {}", isReplay);
+        } finally {
+            writeUnlock();
+        }
+    }
+
     // revoke for roles
     private void revokeInternal(UserIdentity userIdent, List<String> roles, boolean isReplay) throws DdlException {
         writeLock();
@@ -1086,6 +1164,22 @@ public class Auth implements Writable {
             userAuthInfo.add(Joiner.on("; ").join(resourcePrivs));
         }
 
+        // workload group
+        List<String> workloadGroupPrivs = Lists.newArrayList();
+        for (PrivEntry entry : getUserWorkloadGroupPrivTable(userIdent).entries) {
+            WorkloadGroupPrivEntry workloadGroupPrivEntry = (WorkloadGroupPrivEntry) entry;
+            PrivBitSet savedPrivs = workloadGroupPrivEntry.getPrivSet().copy();
+            savedPrivs.or(LdapPrivsChecker.getWorkloadGroupPrivFromLdap(userIdent,
+                    workloadGroupPrivEntry.getOrigWorkloadGroupName()));
+            workloadGroupPrivs.add(workloadGroupPrivEntry.getOrigWorkloadGroupName() + ": " + savedPrivs);
+        }
+
+        if (workloadGroupPrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(workloadGroupPrivs));
+        }
+
         userAuthInfos.add(userAuthInfo);
     }
 
@@ -1149,6 +1243,19 @@ public class Auth implements Writable {
         return table;
     }
 
+    private WorkloadGroupPrivTable getUserWorkloadGroupPrivTable(UserIdentity userIdentity) {
+        WorkloadGroupPrivTable table = new WorkloadGroupPrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getWorkloadGroupPrivTable());
+        }
+        if (isLdapAuthEnabled() && ldapManager.doesUserExist(userIdentity.getQualifiedUser())) {
+            table.merge(
+                    ldapManager.getUserRole(userIdentity.getQualifiedUser()).getWorkloadGroupPrivTable());
+        }
+        return table;
+    }
+
     public List<List<String>> getUserProperties(String qualifiedUser) {
         readLock();
         try {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
index 0d9370393f..6e8916db2d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
@@ -33,6 +33,9 @@ public class PrivPredicate {
     public static final PrivPredicate SHOW_RESOURCES = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
             Privilege.USAGE_PRIV),
             Operator.OR);
+    public static final PrivPredicate SHOW_WORKLOAD_GROUP = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
+                    Privilege.USAGE_PRIV),
+            Operator.OR);
     // create/drop/alter/show user
     public static final PrivPredicate GRANT = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
             Privilege.GRANT_PRIV),
@@ -70,7 +73,7 @@ public class PrivPredicate {
     public static final PrivPredicate OPERATOR = PrivPredicate.of(PrivBitSet.of(Privilege.NODE_PRIV),
             Operator.OR);
 
-    // resource usage
+    // resource/workloadGroup usage
     public static final PrivPredicate USAGE = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
             Privilege.USAGE_PRIV),
             Operator.OR);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
index 82bb9c911f..680bc1dcae 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
@@ -30,7 +30,7 @@ public enum Privilege {
     ALTER_PRIV("Alter_priv", 5, "Privilege for alter database or table"),
     CREATE_PRIV("Create_priv", 6, "Privilege for creating database or table"),
     DROP_PRIV("Drop_priv", 7, "Privilege for dropping database or table"),
-    USAGE_PRIV("Usage_priv", 8, "Privilege for using resource");
+    USAGE_PRIV("Usage_priv", 8, "Privilege for using resource or workloadGroup");
 
     public static Privilege[] privileges = {
             NODE_PRIV,
@@ -55,6 +55,17 @@ public enum Privilege {
             DROP_PRIV
     };
 
+    // only GRANT_PRIV and USAGE_PRIV can grant on workloadGroup
+    public static Privilege[] notBelongToWorkloadGroupPrivileges = {
+            NODE_PRIV,
+            ADMIN_PRIV,
+            SELECT_PRIV,
+            LOAD_PRIV,
+            ALTER_PRIV,
+            CREATE_PRIV,
+            DROP_PRIV
+    };
+
     public static Map<Privilege, String> privInDorisToMysql =
             ImmutableMap.<Privilege, String>builder() // No NODE_PRIV and ADMIN_PRIV in the mysql
                     .put(SELECT_PRIV, "SELECT")
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
index 8ae3ac5b79..a4cb457f18 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
@@ -20,6 +20,7 @@ package org.apache.doris.mysql.privilege;
 import org.apache.doris.analysis.ResourcePattern;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
@@ -30,6 +31,7 @@ import org.apache.doris.common.io.Writable;
 import org.apache.doris.mysql.privilege.Auth.PrivLevel;
 import org.apache.doris.persist.gson.GsonPostProcessable;
 import org.apache.doris.persist.gson.GsonUtils;
+import org.apache.doris.resource.workloadgroup.WorkloadGroupMgr;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
@@ -53,12 +55,21 @@ public class Role implements Writable, GsonPostProcessable {
     // admin is like DBA, who has all privileges except for NODE privilege held by operator
     public static String ADMIN_ROLE = "admin";
 
-    public static Role OPERATOR = new Role(OPERATOR_ROLE,
-            TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV),
-            ResourcePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV));
-    public static Role ADMIN = new Role(ADMIN_ROLE,
-            TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV),
-            ResourcePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+    public static Role OPERATOR;
+    public static Role ADMIN;
+
+    static {
+        try {
+            // Global privileges do not distinguish Pattern, global admin and node privileges contain all privileges
+            // and do not need repeated authorization.
+            OPERATOR = new Role(OPERATOR_ROLE,
+                    TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV, Privilege.ADMIN_PRIV));
+            ADMIN = new Role(ADMIN_ROLE,
+                    TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+        } catch (DdlException e) {
+            LOG.warn("Initialize operator and admin role error.", e);
+        }
+    }
 
     @SerializedName(value = "roleName")
     private String roleName;
@@ -67,6 +78,8 @@ public class Role implements Writable, GsonPostProcessable {
     private Map<TablePattern, PrivBitSet> tblPatternToPrivs = Maps.newConcurrentMap();
     @SerializedName(value = "resourcePatternToPrivs")
     private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = Maps.newConcurrentMap();
+    @SerializedName(value = "workloadGroupPatternToPrivs")
+    private Map<WorkloadGroupPattern, PrivBitSet> workloadGroupPatternToPrivs = Maps.newConcurrentMap();
 
     // Will not be persisted, generated by tblPatternToPrivs and resourcePatternToPrivs
     private GlobalPrivTable globalPrivTable = new GlobalPrivTable();
@@ -74,6 +87,7 @@ public class Role implements Writable, GsonPostProcessable {
     private DbPrivTable dbPrivTable = new DbPrivTable();
     private TablePrivTable tablePrivTable = new TablePrivTable();
     private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
+    private WorkloadGroupPrivTable workloadGroupPrivTable = new WorkloadGroupPrivTable();
 
     @Deprecated
     private Set<UserIdentity> users = Sets.newConcurrentHashSet();
@@ -103,19 +117,24 @@ public class Role implements Writable, GsonPostProcessable {
         grantPrivs(resourcePattern, privs.copy());
     }
 
+    public Role(String roleName, WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs) throws DdlException {
+        this.roleName = roleName;
+        this.workloadGroupPatternToPrivs.put(workloadGroupPattern, privs);
+        grantPrivs(workloadGroupPattern, privs.copy());
+    }
+
     public Role(String roleName, TablePattern tablePattern, PrivBitSet tablePrivs,
-            ResourcePattern resourcePattern, PrivBitSet resourcePrivs) {
+            WorkloadGroupPattern workloadGroupPattern, PrivBitSet workloadGroupPrivs) {
         this.roleName = roleName;
         this.tblPatternToPrivs.put(tablePattern, tablePrivs);
-        this.resourcePatternToPrivs.put(resourcePattern, resourcePrivs);
+        this.workloadGroupPatternToPrivs.put(workloadGroupPattern, workloadGroupPrivs);
         //for init admin role,will not generate exception
         try {
             grantPrivs(tablePattern, tablePrivs.copy());
-            grantPrivs(resourcePattern, resourcePrivs.copy());
+            grantPrivs(workloadGroupPattern, workloadGroupPrivs.copy());
         } catch (DdlException e) {
             LOG.warn("grant failed,", e);
         }
-
     }
 
     public static boolean isDefaultRoleName(String roleName) {
@@ -134,6 +153,10 @@ public class Role implements Writable, GsonPostProcessable {
         return resourcePatternToPrivs;
     }
 
+    public Map<WorkloadGroupPattern, PrivBitSet> getWorkloadGroupPatternToPrivs() {
+        return workloadGroupPatternToPrivs;
+    }
+
     // merge role not check role name.
     public void mergeNotCheck(Role other) throws DdlException {
         for (Map.Entry<TablePattern, PrivBitSet> entry : other.getTblPatternToPrivs().entrySet()) {
@@ -154,6 +177,15 @@ public class Role implements Writable, GsonPostProcessable {
             }
             grantPrivs(entry.getKey(), entry.getValue().copy());
         }
+        for (Map.Entry<WorkloadGroupPattern, PrivBitSet> entry : other.workloadGroupPatternToPrivs.entrySet()) {
+            if (workloadGroupPatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = workloadGroupPatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                workloadGroupPatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
     }
 
     public void merge(Role other) throws DdlException {
@@ -277,6 +309,27 @@ public class Role implements Writable, GsonPostProcessable {
         return Privilege.satisfy(savedPrivs, wanted);
     }
 
+    public boolean checkWorkloadGroupPriv(String workloadGroupName, PrivPredicate wanted) {
+        // For compatibility with older versions, it is not needed to check the privileges of the default group.
+        if (WorkloadGroupMgr.DEFAULT_GROUP_NAME.equals(workloadGroupName)) {
+            return true;
+        }
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        // Workload groups do not support global usage_priv, so only global admin_priv and usage_priv are checked.
+        if (checkGlobalInternal(PrivPredicate.ADMIN, savedPrivs)
+                || checkWorkloadGroupInternal(workloadGroupName, wanted, savedPrivs)) {
+            return true;
+        }
+
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
+    private boolean checkWorkloadGroupInternal(String workloadGroupName, PrivPredicate wanted, PrivBitSet savedPrivs) {
+        workloadGroupPrivTable.getPrivs(workloadGroupName, savedPrivs);
+        return Privilege.satisfy(savedPrivs, wanted);
+    }
+
     public boolean checkHasPriv(PrivPredicate priv, PrivLevel[] levels) {
         for (PrivLevel privLevel : levels) {
             switch (privLevel) {
@@ -322,6 +375,10 @@ public class Role implements Writable, GsonPostProcessable {
         return resourcePrivTable;
     }
 
+    public WorkloadGroupPrivTable getWorkloadGroupPrivTable() {
+        return workloadGroupPrivTable;
+    }
+
     public boolean checkCanEnterCluster(String clusterName) {
         if (checkGlobalPriv(PrivPredicate.ALL)) {
             return true;
@@ -354,6 +411,12 @@ public class Role implements Writable, GsonPostProcessable {
 
     }
 
+    private void grantPrivs(WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs) throws DdlException {
+        if (workloadGroupPattern.getPrivLevel().equals(PrivLevel.WORKLOAD_GROUP)) {
+            grantWorkloadGroupPrivs(workloadGroupPattern.getworkloadGroupName(), privs);
+        }
+    }
+
     private void grantPrivs(TablePattern tblPattern, PrivBitSet privs) throws DdlException {
         // grant privs to user
         switch (tblPattern.getPrivLevel()) {
@@ -428,6 +491,16 @@ public class Role implements Writable, GsonPostProcessable {
         tablePrivTable.addEntry(entry, false, false);
     }
 
+    private void grantWorkloadGroupPrivs(String workloadGroupName, PrivBitSet privs) throws DdlException {
+        WorkloadGroupPrivEntry entry;
+        try {
+            entry = WorkloadGroupPrivEntry.create(workloadGroupName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        workloadGroupPrivTable.addEntry(entry, false, false);
+    }
+
     public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, boolean errOnNonExist) throws DdlException {
         PrivBitSet existingPriv = tblPatternToPrivs.get(tblPattern);
         if (existingPriv == null) {
@@ -474,6 +547,35 @@ public class Role implements Writable, GsonPostProcessable {
         resourcePrivTable.revoke(entry, false, true);
     }
 
+    public void revokePrivs(WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs, boolean errOnNonExist)
+            throws DdlException {
+        PrivBitSet existingPriv = workloadGroupPatternToPrivs.get(workloadGroupPattern);
+        if (existingPriv == null) {
+            if (errOnNonExist) {
+                throw new DdlException(workloadGroupPattern + " does not exist in role " + roleName);
+            }
+            return;
+        }
+        existingPriv.remove(privs);
+        revokePrivs(workloadGroupPattern, privs);
+    }
+
+    private void revokePrivs(WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs) throws DdlException {
+        if (workloadGroupPattern.getPrivLevel().equals(PrivLevel.WORKLOAD_GROUP)) {
+            revokeWorkloadGroupPrivs(workloadGroupPattern.getworkloadGroupName(), privs);
+        }
+    }
+
+    private void revokeWorkloadGroupPrivs(String workloadGroupName, PrivBitSet privs) throws DdlException {
+        WorkloadGroupPrivEntry entry;
+        try {
+            entry = WorkloadGroupPrivEntry.create(workloadGroupName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        workloadGroupPrivTable.revoke(entry, false, true);
+    }
+
     private void revokePrivs(TablePattern tblPattern, PrivBitSet privs) throws DdlException {
         switch (tblPattern.getPrivLevel()) {
             case GLOBAL:
@@ -580,6 +682,7 @@ public class Role implements Writable, GsonPostProcessable {
         StringBuilder sb = new StringBuilder();
         sb.append("role: ").append(roleName).append(", db table privs: ").append(tblPatternToPrivs);
         sb.append(", resource privs: ").append(resourcePatternToPrivs);
+        sb.append(", workload group privs: ").append(workloadGroupPatternToPrivs);
         return sb.toString();
     }
 
@@ -639,6 +742,7 @@ public class Role implements Writable, GsonPostProcessable {
         dbPrivTable = new DbPrivTable();
         tablePrivTable = new TablePrivTable();
         resourcePrivTable = new ResourcePrivTable();
+        workloadGroupPrivTable = new WorkloadGroupPrivTable();
         for (Entry<TablePattern, PrivBitSet> entry : tblPatternToPrivs.entrySet()) {
             try {
                 grantPrivs(entry.getKey(), entry.getValue().copy());
@@ -653,5 +757,12 @@ public class Role implements Writable, GsonPostProcessable {
                 LOG.warn("grant failed,", e);
             }
         }
+        for (Entry<WorkloadGroupPattern, PrivBitSet> entry : workloadGroupPatternToPrivs.entrySet()) {
+            try {
+                grantPrivs(entry.getKey(), entry.getValue().copy());
+            } catch (DdlException e) {
+                LOG.warn("grant failed,", e);
+            }
+        }
     }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
index 633b840477..6d1a669c4f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
@@ -20,6 +20,7 @@ package org.apache.doris.mysql.privilege;
 import org.apache.doris.analysis.ResourcePattern;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.InfoSchemaDb;
 import org.apache.doris.cluster.ClusterNamespace;
@@ -32,6 +33,7 @@ import org.apache.doris.common.io.Writable;
 import org.apache.doris.mysql.privilege.Auth.PrivLevel;
 import org.apache.doris.persist.gson.GsonUtils;
 import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.resource.workloadgroup.WorkloadGroupMgr;
 import org.apache.doris.system.SystemInfoService;
 
 import com.google.common.base.Joiner;
@@ -124,6 +126,20 @@ public class RoleManager implements Writable {
         return existingRole;
     }
 
+    public Role revokePrivs(String role, WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs,
+            boolean errOnNonExist)
+            throws DdlException {
+        Role existingRole = roles.get(role);
+        if (existingRole == null) {
+            if (errOnNonExist) {
+                throw new DdlException("Role " + role + " does not exist");
+            }
+            return null;
+        }
+        existingRole.revokePrivs(workloadGroupPattern, privs, errOnNonExist);
+        return existingRole;
+    }
+
     public void getRoleInfo(List<List<String>> results) {
         for (Role role : roles.values()) {
             if (ClusterNamespace.getNameFromFullName(role.getRoleName()).startsWith(DEFAULT_ROLE_PREFIX)) {
@@ -139,9 +155,12 @@ public class RoleManager implements Writable {
                             role.getTblPatternToPrivs().entrySet().stream()
                                     .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel())).entrySet()
                                     .stream(),
-                            role.getResourcePatternToPrivs().entrySet().stream()
-                                    .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel())).entrySet()
-                                    .stream()
+                            Stream.concat(role.getResourcePatternToPrivs().entrySet().stream()
+                                            .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel()))
+                                            .entrySet().stream(),
+                                    role.getWorkloadGroupPatternToPrivs().entrySet().stream()
+                                            .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel()))
+                                            .entrySet().stream())
                     ).collect(Collectors.toMap(Entry::getKey, entry -> {
                                 if (entry.getKey() == PrivLevel.GLOBAL) {
                                     return entry.getValue().stream().findFirst().map(priv -> priv.getValue().toString())
@@ -177,8 +196,15 @@ public class RoleManager implements Writable {
         } catch (AnalysisException e) {
             LOG.warn("should not happen", e);
         }
-        Role role = new Role(userDefaultRoleName, tblPattern,
-                PrivBitSet.of(Privilege.SELECT_PRIV));
+        // grant read privs of default workload group
+        WorkloadGroupPattern workloadGroupPattern = new WorkloadGroupPattern(WorkloadGroupMgr.DEFAULT_GROUP_NAME);
+        try {
+            workloadGroupPattern.analyze();
+        } catch (AnalysisException e) {
+            LOG.warn("should not happen", e);
+        }
+        Role role = new Role(userDefaultRoleName, tblPattern, PrivBitSet.of(Privilege.SELECT_PRIV),
+                workloadGroupPattern, PrivBitSet.of(Privilege.USAGE_PRIV));
         roles.put(role.getRoleName(), role);
         return role;
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/SystemAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/SystemAccessController.java
index e20f2fad86..59672f2a9c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/SystemAccessController.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/SystemAccessController.java
@@ -40,6 +40,10 @@ public class SystemAccessController {
         return auth.checkResourcePriv(currentUser, resourceName, wanted);
     }
 
+    public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadGroupName, PrivPredicate wanted) {
+        return auth.checkWorkloadGroupPriv(currentUser, workloadGroupName, wanted);
+    }
+
     /*
      * Check if current user has certain privilege.
      * This method will check the given privilege levels
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivEntry.java
new file mode 100644
index 0000000000..a9762b4b32
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivEntry.java
@@ -0,0 +1,98 @@
+// 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.doris.mysql.privilege;
+
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+
+import java.io.DataInput;
+import java.io.IOException;
+
+public class WorkloadGroupPrivEntry extends PrivEntry {
+
+    protected PatternMatcher workloadGroupPattern;
+    protected String origWorkloadGroupName;
+
+    protected WorkloadGroupPrivEntry(PatternMatcher workloadGroupPattern,
+            String origWorkloadGroupName, PrivBitSet privSet) {
+        super(privSet);
+        this.workloadGroupPattern = workloadGroupPattern;
+        this.origWorkloadGroupName = origWorkloadGroupName;
+    }
+
+    public static WorkloadGroupPrivEntry create(String workloadGroupName, PrivBitSet privs)
+            throws AnalysisException, PatternMatcherException {
+        PatternMatcher workloadGroupPattern = PatternMatcher.createMysqlPattern(workloadGroupName,
+                CaseSensibility.WORKLOAD_GROUP.getCaseSensibility());
+        if (privs.containsNodePriv() || privs.containsDbTablePriv()) {
+            throw new AnalysisException(
+                    "Workload group privilege can not contains node or db table privileges: " + privs);
+        }
+        return new WorkloadGroupPrivEntry(workloadGroupPattern,
+                workloadGroupName, privs);
+    }
+
+    public PatternMatcher getWorkloadGroupPattern() {
+        return workloadGroupPattern;
+    }
+
+    public String getOrigWorkloadGroupName() {
+        return origWorkloadGroupName;
+    }
+
+    @Override
+    public int compareTo(PrivEntry other) {
+        if (!(other instanceof WorkloadGroupPrivEntry)) {
+            throw new ClassCastException("cannot cast " + other.getClass().toString() + " to " + this.getClass());
+        }
+
+        WorkloadGroupPrivEntry otherEntry = (WorkloadGroupPrivEntry) other;
+
+        return origWorkloadGroupName.compareTo(otherEntry.origWorkloadGroupName);
+    }
+
+    @Override
+    public boolean keyMatch(PrivEntry other) {
+        if (!(other instanceof WorkloadGroupPrivEntry)) {
+            return false;
+        }
+
+        WorkloadGroupPrivEntry otherEntry = (WorkloadGroupPrivEntry) other;
+        return origWorkloadGroupName.equals(otherEntry.origWorkloadGroupName);
+    }
+
+    @Override
+    public String toString() {
+        return "origWorkloadGroup:" + origWorkloadGroupName + ", priv:" + privSet;
+    }
+
+    @Deprecated
+    public void readFields(DataInput in) throws IOException {
+        super.readFields(in);
+        origWorkloadGroupName = Text.readString(in);
+        try {
+            workloadGroupPattern = PatternMatcher.createMysqlPattern(origWorkloadGroupName,
+                    CaseSensibility.WORKLOAD_GROUP.getCaseSensibility());
+        } catch (PatternMatcherException e) {
+            throw new IOException(e);
+        }
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivTable.java
similarity index 54%
copy from fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java
copy to fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivTable.java
index 6d5da6e65f..994118c743 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/CaseSensibility.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/WorkloadGroupPrivTable.java
@@ -15,36 +15,25 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.common;
+package org.apache.doris.mysql.privilege;
 
-/**
- * CaseSensibility Enum.
- **/
-public enum CaseSensibility {
-    CLUSTER(true),
-    CATALOG(true),
-    DATABASE(true),
-    TABLE(true),
-    ROLLUP(true),
-    PARTITION(false),
-    COLUMN(false),
-    USER(true),
-    ROLE(false),
-    HOST(false),
-    LABEL(false),
-    VARIABLES(true),
-    RESOURCE(true),
-    CONFIG(true),
-    ROUTINE_LOAD(true);
+public class WorkloadGroupPrivTable extends PrivTable {
 
-    private boolean caseSensitive;
+    public void getPrivs(String workloadGroupName, PrivBitSet savedPrivs) {
+        WorkloadGroupPrivEntry matchedEntry = null;
+        for (PrivEntry entry : entries) {
+            WorkloadGroupPrivEntry workloadGroupPrivEntry = (WorkloadGroupPrivEntry) entry;
+            if (!workloadGroupPrivEntry.getWorkloadGroupPattern().match(workloadGroupName)) {
+                continue;
+            }
 
-    private CaseSensibility(boolean caseSensitive) {
-        this.caseSensitive  = caseSensitive;
-    }
+            matchedEntry = workloadGroupPrivEntry;
+            break;
+        }
+        if (matchedEntry == null) {
+            return;
+        }
 
-    public boolean getCaseSensibility() {
-        return caseSensitive;
+        savedPrivs.or(matchedEntry.getPrivSet());
     }
-
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
index c3af6940fe..1b6e448cc8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
@@ -21,6 +21,7 @@ import org.apache.doris.analysis.PasswordOptions;
 import org.apache.doris.analysis.ResourcePattern;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.io.Text;
@@ -42,6 +43,8 @@ public class PrivInfo implements Writable {
     private TablePattern tblPattern;
     @SerializedName(value = "resourcePattern")
     private ResourcePattern resourcePattern;
+    @SerializedName(value = "workloadGroupPattern")
+    private WorkloadGroupPattern workloadGroupPattern;
     @SerializedName(value = "privs")
     private PrivBitSet privs;
     @SerializedName(value = "passwd")
@@ -76,6 +79,7 @@ public class PrivInfo implements Writable {
         this.userIdent = userIdent;
         this.tblPattern = tablePattern;
         this.resourcePattern = null;
+        this.workloadGroupPattern = null;
         this.privs = privs;
         this.passwd = passwd;
         this.role = role;
@@ -86,12 +90,24 @@ public class PrivInfo implements Writable {
             byte[] passwd, String role) {
         this.userIdent = userIdent;
         this.tblPattern = null;
+        this.workloadGroupPattern = null;
         this.resourcePattern = resourcePattern;
         this.privs = privs;
         this.passwd = passwd;
         this.role = role;
     }
 
+    public PrivInfo(UserIdentity userIdent, WorkloadGroupPattern workloadGroupPattern, PrivBitSet privs,
+            byte[] passwd, String role) {
+        this.userIdent = userIdent;
+        this.tblPattern = null;
+        this.resourcePattern = null;
+        this.workloadGroupPattern = workloadGroupPattern;
+        this.privs = privs;
+        this.passwd = passwd;
+        this.role = role;
+    }
+
     // For grant/revoke roles to/from userIdent
     public PrivInfo(UserIdentity userIdent, List<String> roles) {
         this.userIdent = userIdent;
@@ -110,6 +126,10 @@ public class PrivInfo implements Writable {
         return resourcePattern;
     }
 
+    public WorkloadGroupPattern getWorkloadGroupPattern() {
+        return workloadGroupPattern;
+    }
+
     public PrivBitSet getPrivs() {
         return privs;
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
index cb2c151215..31b6d36037 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroup.java
@@ -191,7 +191,7 @@ public class WorkloadGroup implements Writable, GsonPostProcessable {
             if (Double.parseDouble(memoryLimit.substring(0, memoryLimit.length() - 1)) <= 0) {
                 throw new DdlException(memLimitErr);
             }
-        } catch (NumberFormatException  e) {
+        } catch (NumberFormatException e) {
             LOG.debug(memLimitErr, e);
             throw new DdlException(memLimitErr);
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgr.java b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgr.java
index 98a8d6891d..ce8717f861 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgr.java
@@ -21,14 +21,18 @@ import org.apache.doris.analysis.AlterWorkloadGroupStmt;
 import org.apache.doris.analysis.CreateWorkloadGroupStmt;
 import org.apache.doris.analysis.DropWorkloadGroupStmt;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
 import org.apache.doris.common.proc.BaseProcResult;
 import org.apache.doris.common.proc.ProcNodeInterface;
 import org.apache.doris.common.proc.ProcResult;
+import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.persist.DropWorkloadGroupOperationLog;
 import org.apache.doris.persist.gson.GsonPostProcessable;
 import org.apache.doris.persist.gson.GsonUtils;
@@ -102,7 +106,7 @@ public class WorkloadGroupMgr implements Writable, GsonPostProcessable {
     }
 
     public List<TPipelineWorkloadGroup> getWorkloadGroup(ConnectContext context) throws UserException {
-        String groupName = getWorkloadGroupName(context);
+        String groupName = getWorkloadGroupNameAndCheckPriv(context);
         List<TPipelineWorkloadGroup> workloadGroups = Lists.newArrayList();
         readLock();
         try {
@@ -118,7 +122,7 @@ public class WorkloadGroupMgr implements Writable, GsonPostProcessable {
     }
 
     public QueryQueue getWorkloadGroupQueryQueue(ConnectContext context) throws UserException {
-        String groupName = getWorkloadGroupName(context);
+        String groupName = getWorkloadGroupNameAndCheckPriv(context);
         readLock();
         try {
             WorkloadGroup workloadGroup = nameToWorkloadGroup.get(groupName);
@@ -131,7 +135,7 @@ public class WorkloadGroupMgr implements Writable, GsonPostProcessable {
         }
     }
 
-    private String getWorkloadGroupName(ConnectContext context) {
+    private String getWorkloadGroupNameAndCheckPriv(ConnectContext context) throws AnalysisException {
         String groupName = context.getSessionVariable().getWorkloadGroup();
         if (Strings.isNullOrEmpty(groupName)) {
             groupName = Env.getCurrentEnv().getAuth().getWorkloadGroup(context.getQualifiedUser());
@@ -139,6 +143,11 @@ public class WorkloadGroupMgr implements Writable, GsonPostProcessable {
         if (Strings.isNullOrEmpty(groupName)) {
             groupName = DEFAULT_GROUP_NAME;
         }
+        if (!Env.getCurrentEnv().getAccessManager().checkWorkloadGroupPriv(context, groupName, PrivPredicate.USAGE)) {
+            ErrorReport.reportAnalysisException(
+                    "Access denied; you need (at least one of) the %s privilege(s) to use workload group '%s'.",
+                    ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "USAGE/ADMIN", groupName);
+        }
         return groupName;
     }
 
@@ -322,7 +331,11 @@ public class WorkloadGroupMgr implements Writable, GsonPostProcessable {
             readLock();
             try {
                 for (WorkloadGroup workloadGroup : idToWorkloadGroup.values()) {
-                    // need to check workload group privs
+                    if (!Objects.isNull(ConnectContext.get()) && !Env.getCurrentEnv().getAccessManager()
+                            .checkWorkloadGroupPriv(ConnectContext.get(), workloadGroup.getName(),
+                                    PrivPredicate.SHOW_WORKLOAD_GROUP)) {
+                        continue;
+                    }
                     workloadGroup.getProcNodeData(result);
                 }
             } finally {
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
index 8e26cac872..417060de97 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
@@ -28,6 +28,7 @@ import org.apache.doris.analysis.RevokeStmt;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.AccessPrivilege;
 import org.apache.doris.catalog.DomainResolver;
 import org.apache.doris.catalog.Env;
@@ -1814,4 +1815,285 @@ public class AuthTest {
         ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
                 "Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt3.analyze(analyzer));
     }
+
+    @Test
+    public void testWorkloadGroupPriv() {
+        UserIdentity userIdentity = new UserIdentity("testUser", "%");
+        String role = "role0";
+        String workloadGroupName = "g1";
+        WorkloadGroupPattern workloadGroupPattern = new WorkloadGroupPattern(workloadGroupName);
+        String anyWorkloadGroup = "%";
+        WorkloadGroupPattern anyWorkloadGroupPattern = new WorkloadGroupPattern(anyWorkloadGroup);
+        List<AccessPrivilege> usagePrivileges = Lists.newArrayList(AccessPrivilege.USAGE_PRIV);
+        UserDesc userDesc = new UserDesc(userIdentity, "12345", true);
+
+        // ------ grant|revoke workload group to|from user ------
+        // 1. create user with no role
+        CreateUserStmt createUserStmt = new CreateUserStmt(false, userDesc, null);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // 2. grant usage_priv on workload group to user
+        GrantStmt grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, usagePrivileges);
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
+
+        // 3. revoke usage_priv on workload group from user
+        RevokeStmt revokeStmt = new RevokeStmt(userIdentity, null, workloadGroupPattern, usagePrivileges);
+        try {
+            revokeStmt.analyze(analyzer);
+            auth.revoke(revokeStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
+        // 3.1 grant 'notBelongToWorkloadGroupPrivileges'
+        for (int i = 0; i < Privilege.notBelongToWorkloadGroupPrivileges.length; i++) {
+            List<AccessPrivilege> notAllowedPrivileges = Lists.newArrayList(
+                    AccessPrivilege.fromName(Privilege.notBelongToWorkloadGroupPrivileges[i].getName()));
+            grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, notAllowedPrivileges);
+            try {
+                grantStmt.analyze(analyzer);
+                Assert.fail(String.format("Can not grant/revoke %s to/from any other users or roles",
+                        Privilege.notBelongToWorkloadGroupPrivileges[i]));
+            } catch (UserException e) {
+                e.printStackTrace();
+            }
+        }
+        // 4. drop user
+        DropUserStmt dropUserStmt = new DropUserStmt(userIdentity);
+        try {
+            dropUserStmt.analyze(analyzer);
+            auth.dropUser(dropUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // ------ grant|revoke workload group to|from role ------
+        // 1. create role
+        CreateRoleStmt roleStmt = new CreateRoleStmt(role);
+        try {
+            roleStmt.analyze(analyzer);
+            auth.createRole(roleStmt);
+        } catch (UserException e1) {
+            e1.printStackTrace();
+            Assert.fail();
+        }
+        // grant usage_priv on workload group to role
+        grantStmt = new GrantStmt(null, role, workloadGroupPattern, usagePrivileges);
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e1) {
+            e1.printStackTrace();
+            Assert.fail();
+        }
+
+        // 2. create user with role
+        createUserStmt = new CreateUserStmt(false, userDesc, role);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
+
+        // 3. revoke usage_priv on workload group from role
+        revokeStmt = new RevokeStmt(null, role, workloadGroupPattern, usagePrivileges);
+        try {
+            revokeStmt.analyze(analyzer);
+            auth.revoke(revokeStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        // also revoke from user with this role
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
+
+        // 4. drop user and role
+        dropUserStmt = new DropUserStmt(userIdentity);
+        try {
+            dropUserStmt.analyze(analyzer);
+            auth.dropUser(dropUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        DropRoleStmt dropRoleStmt = new DropRoleStmt(role);
+        try {
+            dropRoleStmt.analyze(analyzer);
+            auth.dropRole(dropRoleStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // ------ grant|revoke any workload group to|from user ------
+        // 1. create user with no role
+        createUserStmt = new CreateUserStmt(false, userDesc, null);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // 2. grant usage_priv on workload group '%' to user
+        grantStmt = new GrantStmt(userIdentity, null, anyWorkloadGroupPattern, usagePrivileges);
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, anyWorkloadGroup, PrivPredicate.USAGE));
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName,
+                PrivPredicate.SHOW_WORKLOAD_GROUP));
+
+        // 3. revoke usage_priv on workload group '%' from user
+        revokeStmt = new RevokeStmt(userIdentity, null, anyWorkloadGroupPattern, usagePrivileges);
+        try {
+            revokeStmt.analyze(analyzer);
+            auth.revoke(revokeStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, anyWorkloadGroup, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName,
+                PrivPredicate.SHOW_WORKLOAD_GROUP));
+        Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.SHOW));
+
+        // 4. drop user
+        dropUserStmt = new DropUserStmt(userIdentity);
+        try {
+            dropUserStmt.analyze(analyzer);
+            auth.dropUser(dropUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // ------ grant|revoke any workload group to|from role ------
+        // 1. create role
+        roleStmt = new CreateRoleStmt(role);
+        try {
+            roleStmt.analyze(analyzer);
+            auth.createRole(roleStmt);
+        } catch (UserException e1) {
+            e1.printStackTrace();
+            Assert.fail();
+        }
+        // grant usage_priv on workload group '%' to role
+        grantStmt = new GrantStmt(null, role, anyWorkloadGroupPattern, usagePrivileges);
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e1) {
+            e1.printStackTrace();
+            Assert.fail();
+        }
+
+        // 2. create user with role
+        createUserStmt = new CreateUserStmt(false, userDesc, role);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertTrue(accessManager.checkWorkloadGroupPriv(userIdentity, anyWorkloadGroup, PrivPredicate.USAGE));
+
+        // 3. revoke usage_priv on workload group '%' from role
+        revokeStmt = new RevokeStmt(null, role, anyWorkloadGroupPattern, usagePrivileges);
+        try {
+            revokeStmt.analyze(analyzer);
+            auth.revoke(revokeStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        // also revoke from user with this role
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, workloadGroupName, PrivPredicate.USAGE));
+        Assert.assertFalse(accessManager.checkWorkloadGroupPriv(userIdentity, anyWorkloadGroup, PrivPredicate.USAGE));
+
+        // 4. drop user and role
+        dropUserStmt = new DropUserStmt(userIdentity);
+        try {
+            dropUserStmt.analyze(analyzer);
+            auth.dropUser(dropUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        dropRoleStmt = new DropRoleStmt(role);
+        try {
+            dropRoleStmt.analyze(analyzer);
+            auth.dropRole(dropRoleStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // ------ error case ------
+        boolean hasException = false;
+        createUserStmt = new CreateUserStmt(false, userDesc, null);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // 1. grant db table priv to workload group
+        List<AccessPrivilege> privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, privileges);
+        hasException = false;
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            hasException = true;
+        }
+        Assert.assertTrue(hasException);
+
+        // 2. grant workload group priv to db table
+        TablePattern tablePattern = new TablePattern("db1", "*");
+        GrantStmt grantStmt2 = new GrantStmt(userIdentity, null, tablePattern, usagePrivileges);
+        ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
+                "Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt2.analyze(analyzer));
+
+        // 3. grant workload group prov to role on db.table
+        tablePattern = new TablePattern("db1", "*");
+        GrantStmt grantStmt3 = new GrantStmt(userIdentity, "test_role", tablePattern, usagePrivileges);
+        ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
+                "Can not grant/revoke USAGE_PRIV to/from database or table", () -> grantStmt3.analyze(analyzer));
+    }
 }
diff --git a/fe/fe-core/src/test/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgrTest.java b/fe/fe-core/src/test/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgrTest.java
index 31e6030c84..630c1be21f 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgrTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/resource/workloadgroup/WorkloadGroupMgrTest.java
@@ -24,6 +24,8 @@ import org.apache.doris.catalog.Env;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.UserException;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.persist.EditLog;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.thrift.TPipelineWorkloadGroup;
@@ -49,6 +51,9 @@ public class WorkloadGroupMgrTest {
     @Mocked
     private Env env;
 
+    @Mocked
+    AccessControllerManager accessControllerManager;
+
     private AtomicLong id = new AtomicLong(10);
 
     @Before
@@ -73,6 +78,14 @@ public class WorkloadGroupMgrTest {
                 Env.getCurrentEnv();
                 minTimes = 0;
                 result = env;
+
+                env.getAccessManager();
+                minTimes = 0;
+                result = accessControllerManager;
+
+                accessControllerManager.checkWorkloadGroupPriv((ConnectContext) any, anyString, (PrivPredicate) any);
+                minTimes = 0;
+                result = true;
             }
         };
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org