Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support grant GRANT_PRIV on database or table level #1472

Merged
merged 7 commits into from
Jul 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think TablePattern is a confusing name. It's better you can rename it in later PR

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