在前面介绍过“9.1 安全管理整体架构和代码概览、9.2 安全认证”,本篇我们介绍第9章 安全管理源码解析中“9.3 角色管理”的相关精彩内容介绍。
9.3 角色管理
角色是拥有数据库对象和权限的实体,在不同的环境中角色可以认为是一个用户、一个组或者兼顾两者。角色管理包含了角色的创建、修改、删除、权限授予和回收操作。
9.3.1 角色创建
如果在openGauss上需要创建一个角色,可以使用SQL命令CREATE ROLE,其语法为:
CREATE ROLE role_name [ [ WITH ] option [ ... ] ] [ ENCRYPTED | UNENCRYPTED ] { PASSWORD | IDENTIFIED BY } { 'password' | DISABLE };
创建角色是通过函数CreateRole实现的,其函数接口为:
void CreateRole(CreateRoleStmt* stmt)
其中,CreateRoleStmt为创建角色时所需的数据结构,具体数据结构代码如下:
typedef struct CreateRoleStmt {
NodeTag type;
RoleStmtType stmt_type; /* 将要创建的角色类型 ROLE/USER/GROUP */
char* role; /* 角色名 */
List* options; /* 角色属性列表 */
} CreateRoleStmt;
字段stmt_type是枚举类型,相关代码如下:
typedef enum RoleStmtType {
ROLESTMT_ROLE, /* 代表创建角色 */
ROLESTMT_USER, /* 代表创建用户 */
ROLESTMT_GROUP, /* 代表创建组用户 */
} RoleStmtType;
字段option用来存储角色的属性信息,具体的数据结构为:
typedef struct DefElem {
NodeTag type;
char* defnamespace; /* 节点对应的命名空间 */
char* defname; /* 节点对应的角色属性名 */
Node* arg; /* 表示值或类型名 */
DefElemAction defaction; /* SET/ADD/DROP 等其他未指定的行为 */
} DefElem;
在上述的关键数据结构基础之上,完整的创建角色流程如图9-14所示。
图9-14 openGauss角色创建流程
创建角色时先判断所要创建的角色类型。如果是创建用户,则设置其canlogin属性为true,因为用户默认具有登录权限。而创建角色和创建组时,若角色属性参数没有声明的话,则canlogin属性默认为false。相关代码如下:
/* 默认值可能因原始语句类型而异 */
switch (stmt->stmt_type) {
case ROLESTMT_ROLE:
break;
case ROLESTMT_USER:
canlogin = true;
break;
case ROLESTMT_GROUP:
break;
default:
break;
}
检查完所要创建的角色类型以后,开始循环获取角色属性options中的内容,并将其转换成对应的角色属性值类型。相关代码如下:
/* 从node tree中获取option */
foreach (option, stmt->options) {
DefElem* defel = (DefElem*)lfirst(option);
if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0) {
if (dpassword != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0) {
clean_role_password(dpassword);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Permission denied to create role with option UNENCRYPTED.")));
}
} else if (strcmp(defel->defname, "sysid") == 0) {
ereport(NOTICE, (errmsg("SYSID can no longer be specified")));
} else if (strcmp(defel->defname, "inherit") == 0) {
if (dinherit != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dinherit = defel;
} else if (strcmp(defel->defname, "createrole") == 0) {
if (dcreaterole != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dcreaterole = defel;
} else if (strcmp(defel->defname, "createdb") == 0) {
if (dcreatedb != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dcreatedb = defel;
} else if (strcmp(defel->defname, "useft") == 0) {
if (duseft != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
duseft = defel;
……
根据对应的参数信息转换需要的角色属性值类型,如提取issuper值和createrole值等。相关代码如下:
if (dissuper != NULL)
issuper = intVal(dissuper->arg) != 0;
if (dinherit != NULL)
inherit = intVal(dinherit->arg) != 0;
if (dcreaterole != NULL)
createrole = intVal(dcreaterole->arg) != 0;
if (dcreatedb != NULL)
createdb = intVal(dcreatedb->arg) != 0;
……
在完成了转换以后,将角色属性值以及角色的信息一起构建一个pg_authid的元组,再写回系统表并更新索引。作相关代码如下:
/* 检查pg_authid relation,确认该角色没有存在*/
Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
TupleDesc pg_authid_dsc = RelationGetDescr(pg_authid_rel);
if (OidIsValid(get_role_oid(stmt->role, true))) {
str_reset(password);
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", stmt->role)));
}
……
/* 创建一个插入的tuple */
errno_t errorno = memset_s(new_record, sizeof(new_record), 0, sizeof(new_record));
securec_check(errorno, "\0", "\0");
errorno = memset_s(new_record_nulls, sizeof(new_record_nulls), false, sizeof(new_record_nulls));
securec_check(errorno, "\0", "\0");
new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolauditadmin - 1] = BoolGetDatum(isauditadmin);
new_record[Anum_pg_authid_rolsystemadmin - 1] = BoolGetDatum(issystemadmin);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
……
HeapTuple tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid)) {
HeapTupleSetOid(tuple, u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid);
u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid = InvalidOid;
}
roleid = simple_heap_insert(pg_authid_rel, tuple);
if (IsUnderPostmaster) {
if (OidIsValid(rpoid) && (rpoid != DEFAULT_POOL_OID))
recordDependencyOnRespool(AuthIdRelationId, roleid, rpoid);
u_sess->wlm_cxt->wlmcatalog_update_user = true;
}
……
完成更新以后,将新创建的角色加入指定存在的父角色中。相关代码如下:
/* 将新角色添加到指定的现有角色中 */
foreach (item, addroleto) {
char* oldrolename = strVal(lfirst(item));
Oid oldroleid = get_role_oid(oldrolename, false);
AddRoleMems(
oldrolename, oldroleid, list_make1(makeString(stmt->role)), list_make1_oid(roleid), GetUserId(), false);
}
AddRoleMems(stmt->role, roleid, adminmembers, roleNamesToIds(adminmembers), GetUserId(), true);
AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);
至此就完成了整个角色创建的过程。
9.3.2 角色管理
1. 修改角色属性
修改一个数据库角色可以使用SQL命令ALTER ROLE。角色属性的修改是通过调用AlterRole函数来实现的,该函数只有一个类型为AlterRoleStmt结构的参数。相关代码如下:
typedef struct AlterRoleStmt {
NodeTag type;
char* role; /* 角色的名称 */
List* options; /* 需要修改的属性列表 */
int action; /* +1增加成员关系, -1删除成员关系 */
RoleLockType lockstatus; /* 角色锁定状态 */
} AlterRoleStmt;
修改角色的流程如图9-15所示。
图9-15 openGauss角色管理流程图
调用函数AlterRole修改用户角色属性时,首先循环判断options,依次提取要修改的角色属性;然后查看系统表pg_authid判断是否已存在该角色,如果不存在则提示报错;再进行相应的权限判断,检查执行者是否有权限去更改该角色的属性;最后构建一个新的元组,将要更改的属性更新到新元组中,存入系统表pg_authid。同时AlterRole函数也可以用来调整角色的成员关系,结构体中的action字段值设置为1和-1分别表示增加和删除成员关系,该选项将在授予和回收角色章节具体描述。AlterRole函数的具体实现代码如下:
void AlterRole(AlterRoleStmt* stmt)
{
. . .
/* 循环提取角色的属性options */
foreach (option, stmt->options) {
DefElem* defel = (DefElem*)lfirst(option);
if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0) {
if (dpassword != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0) {
clean_role_password(dpassword);
ereport(ERROR,
(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
errmsg("Permission denied to create role with option UNENCRYPTED.")));
}
} else if (strcmp(defel->defname, "createrole") == 0) {
if (dcreaterole != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dcreaterole = defel;
} else if (strcmp(defel->defname, "inherit") == 0) {
if (dinherit != NULL) {
clean_role_password(dpassword);
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
dinherit = defel;
}
. . .
else {
clean_role_password(dpassword);
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("option \"%s\" not recognized", defel->defname)));
}
}
/* 将提取的属性赋值给对应的变量 */
if (dpassword != NULL && dpassword->arg != NULL) {
head = list_head((List*)dpassword->arg);
if (head != NULL) {
pwdargs = (A_Const*)linitial((List*)dpassword->arg);
if (pwdargs != NULL) {
password = strVal(&pwdargs->val);
}
if (lnext(head)) {
pwdargs = (A_Const*)lsecond((List*)dpassword->arg);
if (pwdargs != NULL) {
replPasswd = strVal(&pwdargs->val);
}
}
}
}
if (dinherit != NULL)
inherit = intVal(dinherit->arg);
if (dcreaterole != NULL)
createrole = intVal(dcreaterole->arg);
. . .
/* 查看要修改的角色是否存在,不存在则提示报错 */
Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role));
if (!HeapTupleIsValid(tuple)) {
str_reset(password);
str_reset(replPasswd);
if (!have_createrole_privilege())
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));
else
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", stmt->role)));
}
roleid = HeapTupleGetOid(tuple);
. . .
/* 检查是否有权限更改相应角色的属性,权限不足则提示报错 */
if (roleid == BOOTSTRAP_SUPERUSERID) {
if (!(issuper < 0 && inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 &&
isauditadmin < 0 && issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL &&
rolemembers == NULL && validBegin == NULL && validUntil == NULL && drespool == NULL &&
dparent == NULL && dnode_group == NULL && dspacelimit == NULL && dtmpspacelimit == NULL &&
dspillspacelimit == NULL)) {
str_reset(password);
str_reset(replPasswd);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Permission denied to change privilege of the initial account.")));
}
}
if (dpassword != NULL && roleid == BOOTSTRAP_SUPERUSERID && GetUserId() != BOOTSTRAP_SUPERUSERID) {
str_reset(password);
str_reset(replPasswd);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Permission denied to change password of the initial account.")));
}
. . .
} else if (!have_createrole_privilege()) {
if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && isauditadmin < 0 &&
issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL && rolemembers == NULL &&
validBegin == NULL && validUntil == NULL && !*respool && !OidIsValid(parentid) && dnode_group == NULL &&
!spacelimit && !tmpspacelimit && !spillspacelimit &&
/* if not superuser or have createrole privilege, permission of lock and unlock is denied */
stmt->lockstatus == DO_NOTHING &&
/* if alter password, it will be handled below */
roleid == GetUserId()) ||
(roleid != GetUserId() && dpassword == NULL)) {
str_reset(password);
str_reset(replPasswd);
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));
}
}
...
/* 将要更改的角色属性值分别更新到新元组中,再将新元组替代旧元组存入系统表pg_authid中 */
if (issuper >= 0) {
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
}
if (inherit >= 0) {
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
}
. . .
HeapTuple new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl);
simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
CatalogUpdateIndexes(pg_authid_rel, new_tuple);
. . .
/* 判断成员关系,增加或删除成员 */
if (stmt->action == 1)
AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);
else if (stmt->action == -1) /* drop members FROM role */
DelRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), false);
. . .
heap_close(pg_authid_rel, NoLock);
}
2. 删除角色
如果要删除一个角色,可以使用SQL命令DROP ROLE。角色的删除是通过调用DropRole函数来实现的,该函数只有一个类型为DropRoleStmt结构的参数。相关代码如下:
typedef struct DropRoleStmt {
NodeTagtype;
List*roles; /* 要删除的角色列表 */
boolmissing_ok; /* 判断角色是否存在 */
boolis_user; /* 要删除的是角色还是用户 */
boolinherit_from_parent; /* 是否继承自父角色*/
DropBehavior behavior; /* 是否级联删除依赖对象 */