Skip to content

Commit

Permalink
Support grant GRANT_PRIV on database or table level (#1472)
Browse files Browse the repository at this point in the history
Currently, GRANT_PRIV can only be granted on global level, which means
it can only be granted on *.*. Grant it on db.* or db.tbl are not allowed.

This will not be able to meet the requirement to create a user who has privilege
to grant privileges to other users on specified database or table, such as:

GRANT SELECT_PRIV ON db1.* TO cmy@'%';

So I extend the range of GRANT_PRIV. User can now grant GRANT_PRIV on
database or even table level, such as:

GRANT GRANT_PRIV ON db1.* TO cmy@'%';

And after being granted, the user cmy@'%' can now grant GRANT_PRIV on db1.* to
other users.
  • Loading branch information
morningman authored Jul 16, 2019
1 parent 4e043e6 commit 2551248
Show file tree
Hide file tree
Showing 20 changed files with 663 additions and 128 deletions.
72 changes: 63 additions & 9 deletions docs/documentation/cn/administrator-guide/privilege.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ Doris 新的权限管理系统参照了 Mysql 的权限管理机制,做到了
9. 查看已创建的角色:SHOW ROELS
10. 查看用户属性:SHOW PROPERTY

关于以上命令的详细帮助,可以通过 mysql 客户端连接 Doris 后,使用 help + command 获取帮助。如 `help create user`
关于以上命令的详细帮助,可以通过 mysql 客户端连接 Doris 后,使用 help + command 获取帮助。如 `HELP CREATE USER`

## 权限说明
## 权限类型

Doris 目前支持以下几种权限

Expand All @@ -61,15 +61,53 @@ Doris 目前支持以下几种权限

5. Alter_priv

对数据库、表的更改权限。包括重命名 库/表、添加/删除/变更 列等操作
对数据库、表的更改权限。包括重命名 库/表、添加/删除/变更 列、添加/删除 分区等操作

6. Create_priv

创建数据库、表的权限
创建数据库、表、视图的权限

7. Drop_priv

删除数据库、表的权限。
删除数据库、表、视图的权限。

## 权限层级

同时,根据权限适用范围的不同,我们将权限分为以下三个层级:

1. GLOBAL LEVEL:全局权限。即通过 GRANT 语句授予的 `*.*` 上的权限。被授予的权限适用于任意数据库中的任意表。
2. DATABASE LEVEL:数据库级权限。即通过 GRANT 语句授予的 `db.*` 上的权限。被授予的权限适用于指定数据库中的任意表。
3. TABLE LEVEL:表级权限。即通过 GRANT 语句授予的 `db.tbl` 上的权限。被授予的权限适用于指定数据库中的指定表。


## ADMIN/GRANT 权限说明

ADMIN\_PRIV 和 GRANT\_PRIV 权限同时拥有**“授予权限”**的权限,较为特殊。这里对和这两个权限相关的操作逐一说明。

1. CREATE USER

* 拥有 ADMIN 权限,或任意层级的 GRANT 权限的用户可以创建新用户。

2. DROP USER

* 只有 ADMIN 权限可以删除用户。

3. CREATE/DROP ROLE

* 只有 ADMIN 权限可以创建角色。

4. GRANT/REVOKE

* 拥有 ADMIN 权限,或者 GLOBAL 层级 GRANT 权限的用户,可以授予或撤销任意用户的权限。
* 拥有 DATABASE 层级 GRANT 权限的用户,可以授予或撤销任意用户对指定数据库的权限。
* 拥有 TABLE 层级 GRANT 权限的用户,可以授予或撤销任意用户对指定数据库中指定表的权限。

5. SET PASSWORD

* 拥有 ADMIN 权限,或者 GLOBAL 层级 GRANT 权限的用户,可以设置任意用户的密码。
* 普通用户可以设置自己对应的 UserIdentity 的密码。自己对应的 UserIdentity 可以通过 `SELECT CURRENT_USER();` 命令查看。
* 拥有非 GLOBAL 层级 GRANT 权限的用户,不可以设置已存在用户的密码,仅能在创建用户时指定密码。


## 一些说明

Expand Down Expand Up @@ -116,8 +154,6 @@ Doris 目前支持以下几种权限
CREATE USER cmy@'192.%' IDENTIFIED BY "abcde";

在优先级上,'192.%' 优先于 '%',因此,当用户 cmy 从 192.168.1.1 这台机器尝试使用密码 '12345' 登陆 Doris 会被拒绝。

3. 权限冲突

5. 忘记密码

Expand All @@ -129,6 +165,24 @@ Doris 目前支持以下几种权限

6. 任何用户都不能重置 root 用户的密码,除了 root 用户自己。

7. 拥有 GRANT 权限的用户可以设置密码。如果没有 GRANT 用户,则用户仅可以设置自己对应的 UserIdentity 的密码。自己对应的 UserIdentity 可以通过 `SELECT CURRENT_USER();` 命令查看。
7. ADMIN\_PRIV 权限只能在 GLOBAL 层级授予或撤销。

8. 拥有 GLOBAL 层级 GRANT_PRIV 其实等同于拥有 ADMIN\_PRIV,因为该层级的 GRANT\_PRIV 有授予任意权限的权限,请谨慎使用。

## 最佳实践

这里举例一些 Doris 权限系统的使用场景。

1. 场景一

8. ADMIN\_PRIV 和 GRANT\_PRIV 权限只能 `GRANT ON *.*``REVOKE FROM *.*`。因为对于指定的库和表,这两个权限没有意义。同时,拥有 GRANT_PRIV 其实等同于拥有 ADMIN\_PRIV,因为 GRANT\_PRIV 有授予任意权限的权限,请谨慎使用。
Doris 集群的使用者分为管理员(Admin)、开发工程师(RD)和用户(Client)。其中管理员拥有整个集群的所有权限,主要负责集群的搭建、节点管理等。开发工程师负责业务建模,包括建库建表、数据的导入和修改等。用户访问不同的数据库和表来获取数据。

在这种场景下,可以为管理员赋予 ADMIN 权限或 GRANT 权限。对 RD 赋予对任意或指定数据库表的 CREATE、DROP、ALTER、LOAD、SELECT 权限。对 Client 赋予对任意或指定数据库表 SELECT 权限。同时,也可以通过创建不同的角色,来简化对多个用户的授权操作。

2. 场景二

一个集群内有多个业务,每个业务可能使用一个或多个数据。每个业务需要管理自己的用户。在这种场景下。管理员用户可以为每个数据库创建一个拥有 DATABASE 层级 GRANT 权限的用户。该用户仅可以对用户进行指定的数据库的授权。




1 change: 1 addition & 0 deletions docs/help/Contents/Account Management/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris

NODE_PRIV:集群节点操作权限,包括节点上下线等操作,只有 root 用户有该权限,不可赋予其他用户。
ADMIN_PRIV:除 NODE_PRIV 以外的所有权限。
GRANT_PRIV: 操作权限的权限。包括创建删除用户、角色,授权和撤权,设置密码等。
SELECT_PRIV:对指定的库或表的读取权限
LOAD_PRIV:对指定的库或表的导入权限
ALTER_PRIV:对指定的库或表的schema变更权限
Expand Down
10 changes: 10 additions & 0 deletions fe/src/main/java/org/apache/doris/analysis/CreateRoleStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@

package org.apache.doris.analysis;

import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeNameFormat;
import org.apache.doris.common.UserException;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;

public class CreateRoleStmt extends DdlStmt {

Expand All @@ -38,6 +43,11 @@ public void analyze(Analyzer analyzer) throws UserException {
super.analyze(analyzer);
FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not create role");
role = ClusterNamespace.getFullName(analyzer.getClusterName(), role);

// check if current user has GRANT priv on GLOBAL level.
if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER");
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@

import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeNameFormat;
import org.apache.doris.common.UserException;
import org.apache.doris.mysql.MysqlPassword;
import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel;
import org.apache.doris.mysql.privilege.PaloRole;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;
Expand Down Expand Up @@ -105,7 +105,7 @@ public boolean needAuditEncryption() {
}

@Override
public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
public void analyze(Analyzer analyzer) throws UserException {
super.analyze(analyzer);
userIdent.analyze(analyzer.getClusterName());
// convert plain password to hashed password
Expand All @@ -129,8 +129,9 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
role = ClusterNamespace.getFullName(analyzer.getClusterName(), role);
}

if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER");
// check if current user has GRANT priv on GLOBAL or DATABASE level.
if (!Catalog.getCurrentCatalog().getAuth().checkHasPriv(ConnectContext.get(), PrivPredicate.GRANT, PrivLevel.GLOBAL, PrivLevel.DATABASE)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
}
}

Expand Down
10 changes: 10 additions & 0 deletions fe/src/main/java/org/apache/doris/analysis/DropRoleStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@

package org.apache.doris.analysis;

import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeNameFormat;
import org.apache.doris.common.UserException;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;

public class DropRoleStmt extends DdlStmt {

Expand All @@ -38,6 +43,11 @@ public void analyze(Analyzer analyzer) throws UserException {
super.analyze(analyzer);
FeNameFormat.checkRoleName(role, false /* can not be superuser */, "Can not drop role");
role = ClusterNamespace.getFullName(analyzer.getClusterName(), role);

// check if current user has GRANT priv on GLOBAL level.
if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER");
}
}

@Override
Expand Down
5 changes: 2 additions & 3 deletions fe/src/main/java/org/apache/doris/analysis/DropUserStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
throw new AnalysisException("Can not drop user with specified host: " + userIdent.getHost());
}

// check authenticate
// only user with GLOBAL level's GRANT_PRIV can drop user.
if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"DROP USER");
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "DROP USER");
}
}

Expand Down
66 changes: 42 additions & 24 deletions fe/src/main/java/org/apache/doris/analysis/GrantStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
import java.util.List;

// GRANT STMT
// grant privilege to some user, this is an administrator operation.
//
// GRANT privilege [, privilege] ON db.tbl TO user [ROLE 'role'];
public class GrantStmt extends DdlStmt {
private UserIdentity userIdent;
Expand Down Expand Up @@ -83,7 +81,8 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
if (userIdent != null) {
userIdent.analyze(analyzer.getClusterName());
} else {
FeNameFormat.checkUserName(role);
FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not grant to role");
role = ClusterNamespace.getFullName(analyzer.getClusterName(), role);
}

tblPattern.analyze(analyzer.getClusterName());
Expand All @@ -92,32 +91,51 @@ public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
throw new AnalysisException("No privileges in grant statement.");
}

// can not grant NODE_PRIV to any other user(root has NODE_PRIV, no need to grant)
for (PaloPrivilege paloPrivilege : privileges) {
if (paloPrivilege == PaloPrivilege.NODE_PRIV) {
throw new AnalysisException("Can not grant NODE_PRIV to any other users or roles");
}
}
checkPrivileges(analyzer, privileges, role, tblPattern);
}

// ADMIN_PRIV and GRANT_PRIV can only be granted as global
if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL) {
for (PaloPrivilege paloPrivilege : privileges) {
if (paloPrivilege == PaloPrivilege.ADMIN_PRIV || paloPrivilege == PaloPrivilege.GRANT_PRIV) {
throw new AnalysisException(
"Can not grant ADMIN_PRIV or GRANT_PRIV to specified database or table. Only support to *.*");
}
}
/*
* Rules:
* 1. Can not grant/revoke NODE_PRIV to/from any other user.
* 2. ADMIN_PRIV can only be granted/revoked on GLOBAL level
* 3. Privileges can not be granted/revoked to/from ADMIN and OPERATOR role
* 4. Only user with GLOBAL level's GRANT_PRIV can grant/revoke privileges to/from roles.
* 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.
*/
public static void checkPrivileges(Analyzer analyzer, List<PaloPrivilege> privileges,
String role, TablePattern tblPattern) throws AnalysisException {
// Rule 1
if (privileges.contains(PaloPrivilege.NODE_PRIV)) {
throw new AnalysisException("Can not grant NODE_PRIV to any other users or roles");
}

if (role != null) {
// can not grant to admin or operator role
FeNameFormat.checkRoleName(role, false /* can not be admin */, "Can not grant to role");
role = ClusterNamespace.getFullName(analyzer.getClusterName(), role);
// Rule 2
if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && privileges.contains(PaloPrivilege.ADMIN_PRIV)) {
throw new AnalysisException("ADMIN_PRIV privilege can only be granted on *.*");
}

if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"GRANT");
if (role != null) {
// Rule 3 and 4
if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
}
} else {
// Rule 5.1 and 5.2
if (tblPattern.getPrivLevel() == PrivLevel.GLOBAL) {
if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
}
} else if (tblPattern.getPrivLevel() == PrivLevel.DATABASE){
if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), tblPattern.getQuolifiedDb(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
}
} else {
// table level
if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tblPattern.getQuolifiedDb(), tblPattern.getTbl(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
}
}
}
}

Expand Down
29 changes: 2 additions & 27 deletions fe/src/main/java/org/apache/doris/analysis/RevokeStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,11 @@
package org.apache.doris.analysis;

import org.apache.doris.catalog.AccessPrivilege;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.FeNameFormat;
import org.apache.doris.mysql.privilege.PaloAuth.PrivLevel;
import org.apache.doris.mysql.privilege.PaloPrivilege;
import org.apache.doris.mysql.privilege.PrivBitSet;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
Expand Down Expand Up @@ -87,27 +81,8 @@ public void analyze(Analyzer analyzer) throws AnalysisException {
throw new AnalysisException("No privileges in revoke statement.");
}

// can not revoke NODE_PRIV from any user
for (PaloPrivilege paloPrivilege : privileges) {
if (paloPrivilege == PaloPrivilege.NODE_PRIV) {
throw new AnalysisException("Can not revoke NODE_PRIV from any users or roles");
}
}

// ADMIN_PRIV and GRANT_PRIV can only be revoked as global
if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL) {
for (PaloPrivilege paloPrivilege : privileges) {
if (paloPrivilege == PaloPrivilege.ADMIN_PRIV || paloPrivilege == PaloPrivilege.GRANT_PRIV) {
throw new AnalysisException(
"Can not revoke ADMIN_PRIV or GRANT_PRIV from specified database or table. Only support from *.*");
}
}
}

if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
"REVOKE");
}
// Revoke operation obey the same rule as Grant operation. reuse the same method
GrantStmt.checkPrivileges(analyzer, privileges, role, tblPattern);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static DbPrivEntry create(String host, String db, String user, PrivBitSet

PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility());

if (privs.containsNodeOrGrantPriv()) {
if (privs.containsNodePriv()) {
throw new AnalysisException("Db privilege can not contains global privileges: " + privs);
}

Expand Down
Loading

0 comments on commit 2551248

Please sign in to comment.