目录
[3.1 角色创建](#3.1 角色创建)
[3.2 角色管理](#3.2 角色管理)
书接上文openGauss安全整体架构&安全认证,从安全整体架构 与安全认证两方面,对高斯数据库的高安全性能进行了解读,本篇我们将从角色创建和角色管理两方面对高斯数据库的角色权限进行介绍。
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;
有了上述的关键数据结构,完整的创建角色流程:
图1 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中获取optionforeach (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);
此时,完成整个角色创建的过程。
3.2 角色管理
(1)修改角色属性
如果要修改一个数据库角色,可以使用SQL命令ALTER ROLE。角色属性的修改是通过调用AlterRole函数来实现的,该函数只有一个类型为AlterRoleStmt结构的参数。
typedef struct AlterRoleStmt { NodeTag type; char* role; // 角色的名称 List* options; // 需要修改的属性列表 int action; // +1增加成员关系, -1删除成员关系 RoleLockType lockstatus; // 角色锁定状态} AlterRoleStmt;
修改角色的流程如图2所示。
图2 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; // 是否级联删除依赖对象} DropRoleStmt;
删除角色的流程如图3所示。
图3 openGauss角色删除流程图
首先判断当前操作者是否有权限执行该操作,若没有则报错退出;然后检查待删除的角色是否存在,若不存在,则根据missing_ok选择返回ERROR或NOTICE提示信息;再通过扫描系统表pg_authid和pg_auth_members,删除所有涉及待删除角色的元组执行;若behavior取值DROP_CASCADE,则级联删除该角色所拥有的所有数据库对象;最后删除该角色在系统表pg_auth_history和pg_user_status中对应的信息,具体的实现过程如下:
void DropRole(DropRoleStmt* stmt){ . . .// 检查执行者是否有权限删除角色。 if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied to drop role.")));// 循环处理要删除的角色。 foreach (item, stmt->roles) {. . .// 检查要删除的角色是否存在,若不存在则提示报错。 HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); if (!HeapTupleIsValid(tuple)) { if (!stmt->missing_ok) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", role))); } else { ereport(NOTICE, (errmsg("role \"%s\" does not exist, skipping", role))); } continue; } . . .// 当前用户不允许删除。 if (roleid == GetUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped"))); if (roleid == GetOuterUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped"))); if (roleid == GetSessionUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("session user cannot be dropped"))); // 校验执行者和被删除角色的权限,如系统管理员才有权限删除其他系统管理员 if((((Form_pg_authid)GETSTRUCT(tuple))->rolsuper|| ((Form_pg_authid)GETSTRUCT(tuple))->rolsystemadmin) && !isRelSuperuser()) ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); if ((((Form_pg_authid)GETSTRUCT(tuple))->rolauditadmin) && g_instance.attr.attr_security.enablePrivilegesSeparate && !isRelSuperuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); . . . // 针对CASCADE的情况,删除该角色拥有的对象。 if (stmt->behavior == DROP_CASCADE) { char* user = NULL; CancelQuery(role); user = (char*)palloc(sizeof(char) * strlen(role) + 1); errno_t errorno = strncpy_s(user, strlen(role) + 1, role, strlen(role)); securec_check(errorno, "\0", "\0"); drop_objectstmt.behavior = stmt->behavior; drop_objectstmt.type = T_DropOwnedStmt; drop_objectstmt.roles = list_make1(makeString(user));
DropOwnedObjects(&drop_objectstmt); list_free_deep(drop_objectstmt.roles); }
// 检查是否有对象依赖于该角色,若还存在依赖,则提示报错。 if (checkSharedDependencies(AuthIdRelationId, roleid, &detail, &detail_log)) ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("role \"%s\" cannot be dropped because some objects depend on it", role), errdetail_internal("%s", detail), errdetail_log("%s", detail_log))); // 从相关系统表中删除涉及待删除角色的元组。 simple_heap_delete(pg_authid_rel, &tuple->t_self);. . . while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) { simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self); }
systable_endscan(sscan); DropAuthHistory(roleid); DropUserStatus(roleid); DeleteSharedComments(roleid, AuthIdRelationId); DeleteSharedSecurityLabel(roleid, AuthIdRelationId); DropSetting(InvalidOid, roleid);. . . heap_close(pg_auth_members_rel, NoLock); heap_close(pg_authid_rel, NoLock);}
(3)授予和回收角色
如果要授予或回收角色的成员关系,可以使用SQL命令GRANT/REVOKE。如果声明了WITH ADMIN OPTION选项,那么被加入的成员角色还可以将其他角色加入到父角色中。角色的授予或回收通过调用GrantRole函数来实现,该函数只有一个类型为GrantRoleStmt结构的参数。
typedef struct GrantRoleStmt { NodeTag type; List* granted_roles;// 被授予或回收的角色集合 List* grantee_roles;// 从granted_roles中增加或删除的角色集合 Bool is_grant;// true代表授权,false代表回收 Bool admin_opt;// 是否带有with admin option选项 char* grantor;// 授权者 Drop Behaviorbehavior;// 是否级联回收角色} GrantRoleStmt;
授予角色时,grantee_roles中的角色将被添加到granted_roles,调用函数AddRoleMems实现;回收角色时,将grantee_roles中的角色从granted_roles中删除,调用函数DelRoleMems实现。
AddRoleMems的实现流程如图4所示。
图4 openGauss增加用户成员流程图
函数AddRoleMems的具体实现如下,其中:
-
rolename和roleid分别表示要被加入成员的角色的名称和oid。
-
memberNames和memberIds分别是要添加的角色名称和oid的列表。
-
grantorId表示授权者的oid。
-
admin_opt表示是否带有with admin option选项。
static void AddRoleMems( const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt){. . . // 校验执行者的权限。 if (superuser_arg(roleid)) { if (!isRelSuperuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); . . . } . . . if (grantorId != GetUserId() && !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to set grantor")));// 循环处理要添加的角色。 pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock); pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
forboth(nameitem, memberNames, iditem, memberIds) { // 针对角色和成员信息创建pg_auth_members元组,再将新元组插入到系统表中。. . . new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); new_record[Anum_pg_auth_members_member -1] = ObjectIdGetDatum(memberid); new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
if (HeapTupleIsValid(authmem_tuple)) { new_record_repl[Anum_pg_auth_members_grantor - 1] = true; new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl); simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple); CatalogUpdateIndexes(pg_authmem_rel, tuple); ReleaseSysCache(authmem_tuple); } else { tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls); (void)simple_heap_insert(pg_authmem_rel, tuple); CatalogUpdateIndexes(pg_authmem_rel, tuple); } }. . . heap_close(pg_authmem_rel, NoLock);}
函数DelRoleMems的实现过程类似。首先对执行者的相关权限进行校验,然后循环处理要删除的角色,删除系统表pg_auth_member中相关的元组。
以上内容从角色创建和角色管理两方面对角色权限进行了介绍,下篇将从权限管理和权限检查方面继续解读高斯数据库的角色权限,敬请期待~