
学习 探索 分享数据库前沿知识和技术 共建数据库技术交流圈
上篇图文,我们分享了安全管理源码解析——角色管理的精彩内容,本文将接着介绍对象权限管理的相关内容。
9.4 对象权限管理
9.4.1 权限管理
1. 访问控制列表
typedef struct AclItem {Oid ai_grantee; /* 被授权者的OID */Oid ai_grantor; /* 授权者的OID */AclMode ai_privs; /* 权限位:32位的比特位 */} AclItem;
其中ai_privs字段是AclMode类型。AclMode是一个32位的比特位。其高16位为权限选项位,当该比特位取值为1时,表示AclItem中的ai_grantee对应的用户具有此对象的相应操作的授权权限,否则表示用户没有授权权限;低16位为操作权限位,当该比特位取值为1时,表示AclItem中的ai_grantee对应的用户具有此对象的相应操作权限,否则表示用户没有相应的权限。在AclMode的结构位图1中,Grant Option记录各权限位的权限授予或被转授情况。低16位记录各权限的授予情况,当授权语句使用ALL时,则表示对象的所有权限。



2. 对象权限管理
数据库对象权限管理主要通过使用SQL命令“GRANT/REVOKE”授予或回收一个或多个角色在对象上的权限。“GRANT/REVOKE”命令都由函数ExecuteGrantStmt实现,该函数只有一个GrantStmt类型的参数,基本执行流程如图4所示。

typedef struct GrantStmt {NodeTag type;bool is_grant; /* true = 授权, false = 回收 */GrantTargetType targtype; /* 操作目标的类型 */GrantObjectType objtype; /* 被操作对象的类型:表、数据库、模式、函数等 */List* objects; /* 被操作对象的集合 */List* privileges; /* 要操作权限列表 */List* grantees; /* 被授权者的集合 */bool grant_option; /* true = 再授予权限 */DropBehavior behavior; /* 回收权限的行为 */} GrantStmt;
typedef struct InternalGrant {bool is_grant; /* true=授权, false=回收 */GrantObjectType objtype; /* 被操作对象的类型:表、数据库、模式、函数等 */List* objects; /* 被操作对象的集合 */bool all_privs; /* 是否授予或回收所有的权限 */AclMode privileges; /* AclMode形式表示的DML类操作对应的权限 */AclMode ddl_privileges; /* AclMode形式表示的DDL类操作对应的权限 */List* col_privs; /* 对列执行的DML类操作对应的权限 */List* col_ddl_privs; /* 对列执行的DDL类操作对应的权限 */List* grantees; /* 被授权者的集合 */bool grant_option; /* true=再授予权限 */DropBehavior behavior; /* 回收权限的行为 */} InternalGrant;
函数ExecuteGrantStmt在完成结构转换之后,调用函数ExecGrantStmt_oids,根据对象类型分别调用相应对象的权限管理函数。接下来以表对象的权限管理过程为例介绍权限管理的算法。函数ExecGrant_Relation用来处理表对象权限的授予或回收操作,入参为InternalGrant类型的变量,存储着授权或回收操作的操作对象信息、被授权者信息和权限信息。函数ExecGrant_Relation的处理流程如图5所示。

(1) 从系统表pg_class中获取旧ACL。如果不存在旧的ACL,则新建一个ACL,并调用函数acldefault将默认的权限信息赋给该ACL。根据对象的不同,初始的缺省权限含有部分可赋予PUBLIC的权限。如果存在旧的ACL,则将旧的ACL存储为一个副本。
(2) 调用select_best_grantor函数来获取授权者对操作对象所拥有的授权权限avail_goptions;将参数avail_goptions传入函数restrict_and_check_grant,结合SQL命令中给出的操作权限,计算出实际需要授予或回收的权限。
(3) 调用merge_acl_with_grant函数生成新的ACL。如果是授予权限,则将要授予的权限添加到旧ACL中;如果是回收权限,则将要被回收的权限从旧ACL中删除。
(4) 将新的ACL更新到系统表pg_class对应元组的ACL字段,完成授权或回收过程。
static void ExecGrant_Relation(InternalGrant* istmt){. . ./* 循环处理每一个表对象 */foreach (cell, istmt->objects) {. . ./* 判断所要操作的表对象是否存在,若不存在则提示报错 */tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));if (!HeapTupleIsValid(tuple))ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relOid)));pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple);. . ./* 系统表pg_class中获取旧ACL。若不存在旧的ACL,则新建一个ACL,若存在旧的ACL,则将旧的ACL存储为一个副本 */ownerId = pg_class_tuple->relowner;aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, &isNull);if (isNull) {switch (pg_class_tuple->relkind) {case RELKIND_SEQUENCE:old_acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId);break;default:old_acl = acldefault(ACL_OBJECT_RELATION, ownerId);break;}noldmembers = 0;oldmembers = NULL;} else {old_acl = DatumGetAclPCopy(aclDatum);noldmembers = aclmembers(old_acl, &oldmembers);}old_rel_acl = aclcopy(old_acl);/* 处理表级别的权限 */if (this_privileges != ACL_NO_RIGHTS) {AclMode avail_goptions;Acl* new_acl = NULL;Oid grantorId;HeapTuple newtuple = NULL;Datum values[Natts_pg_class];bool nulls[Natts_pg_class] = {false};bool replaces[Natts_pg_class] = {false};int nnewmembers;Oid* newmembers = NULL;AclObjectKind aclkind;/* 获取授权者grantorId和授权者对该操作对象所拥有的授权权限avail_goptions */select_best_grantor(GetUserId(), this_privileges, old_acl, ownerId, &grantorId, &avail_goptions);switch (pg_class_tuple->relkind) {case RELKIND_SEQUENCE:aclkind = ACL_KIND_SEQUENCE;break;default:aclkind = ACL_KIND_CLASS;break;}/* 结合参数avail_goptions和SQL命令中给出的操作权限,计算出实际需要授予或回收的权限 */this_privileges = restrict_and_check_grant(istmt->is_grant,avail_goptions,istmt->all_privs,this_privileges,relOid,grantorId,aclkind,NameStr(pg_class_tuple->relname),0,NULL);/* 生成新的ACL,并更新到系统表pg_class对应元组的ACL字段 */new_acl = merge_acl_with_grant(old_acl,istmt->is_grant,istmt->grant_option,istmt->behavior,istmt->grantees,this_privileges,grantorId,ownerId);. . .replaces[Anum_pg_class_relacl - 1] = true;values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);simple_heap_update(relation, &newtuple->t_self, newtuple);. . .}/* 若存在列级授权或回收,则调用ExecGrant_Attribute 函数处理 */. . .if (have_col_privileges) {AttrNumber i;for (i = 0; i < num_col_privileges; i++) {if (col_privileges[i] == ACL_NO_RIGHTS)continue;ExecGrant_Attribute(istmt,relOid,NameStr(pg_class_tuple->relname),i + FirstLowInvalidHeapAttributeNumber,ownerId,col_privileges[i],attRelation,old_rel_acl);}}. . .}heap_close(attRelation, RowExclusiveLock);heap_close(relation, RowExclusiveLock);}
9.4.2 权限检查
用户在对数据库对象进行访问操作时,数据库会检查用户是否拥有该对象的操作权限。通常数据库对象的所有者和初始用户(superuser)拥有该对象的全部操作权限,其他普通用户需要被授予权限才可以执行相应操作。数据库通过查询数据库对象的访问控制列表检查用户对数据库对象的访问权限,数据库对象的ACL保存在对应的系统表中,当被授予或回收对象权限时,系统表中保存的ACL权限位会被更新。常用的数据库对象权限检查函数、ACL检查函数、ACL所在系统表以及对象所有者检查函数对应关系如表2所示。

下面以表的权限检查为例进行权限检查过程说明。表权限检查函数pg_class_aclcheck的定义代码如下:
AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode, bool check_nodegroup){if (pg_class_aclmask(table_oid, roleid, mode, ACLMASK_ANY, check_nodegroup) != 0)return ACLCHECK_OK;elsereturn ACLCHECK_NO_PRIV;}
以上内容为安全管理源码解析—对象权限管理的相关内容,下篇图文,将详细介绍“审计与追踪”的相关内容,敬请期待!
- END -
【视频号今日推荐】




