写点什么

openGauss 数据库源码解析系列文章——安全管理源码解析(四)

作者:daydayup
  • 2023-08-12
    北京
  • 本文字数:5109 字

    阅读完需:约 17 分钟

四、对象权限管理


权限管理是安全管理重要的一环,openGauss 权限管理基于访问控制列表(access control list,ACL)实现。


4.1 权限管理


1. 访问控制列表


访问控制列表是实现数据库对象权限管理的基础,每个对象都具有 ACL,存储该对象的所有授权信息。当用户访问对象时,只有用户在对象的 ACL 中并且具有所需的权限才能够访问该对象。


每个 ACL 是由 1 个或多个 AclItem 构成的链表,每 1 个 AclItem 由授权者、被授权者和权限位 3 部分构成,记录着可在对象上进行操作的用户及其权限。


数据结构 AclItem 的代码如下:


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 的结构位图 18 中,Grant Option 记录各权限位的权限授予或被转授情况。低 16 位记录各权限的授予情况,当授权语句使用 ALL 时,则表示对象的所有权限。



图 18 openGauss AclMode 结构图


openGauss 将执行 DML 类操作和 DDL 类操作的权限分别记在两个 AclMode 结构中,并以第 15 位的值来区分 2 者,从而实现对于每一个数据库对象,相同的授权者和被授权者对应两个不同的 AclMode,分别表示记录 DML 类操作权限和 DDL 类操作权限。实现方式如图 19 和图 20 所示。



图 19 openGauss 记录 DML 类操作权限的 AclMode 结构



图 20 openGauss 记录 DDL 类操作权限的 AclMode 结构


每个权限参数代表的权限如表 4 所示。


表 4 权限参数



2. 对象权限管理


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



图 21 函数 ExecuteGrantStmt 执行流程


数据结构 GrantStmt 定义代码如下:


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;
复制代码


函数 ExecuteGrantStmt 首先将 GrantStmt 结构转换为 InternalGrant 结构,并将权限列表转换为内部的 AclMode 表示形式。当 privileges 取值为 NIL 时,表示授予或回收所有的权限,此时置 InternalGrant 的 all_privs 字段为 true,privileges 字段为 ACL_NO_RIGHTS。


数据结构 InternalGrant 的代码如下:


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 的处理流程如图 22 所示。



图 22 函数 ExecGrant_Relation 的处理流程


该函数的处理流程为:


(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);}
复制代码


4.2 权限检查


用户在对数据库对象进行访问操作时,数据库会检查用户是否拥有该对象的操作权限。通常数据库对象的所有者和初始用户(superuser)拥有该对象的全部操作权限,其他普通用户需要被授予权限才可以执行相应操作。数据库通过查询数据库对象的访问控制列表检查用户对数据库对象的访问权限,数据库对象的 ACL 保存在对应的系统表中,当被授予或回收对象权限时,系统表中保存的 ACL 权限位会被更新。常用的数据库对象权限检查函数、ACL 检查函数、ACL 所在系统表以及对象所有者检查函数对应关系如表 5 所示。


表 5 数据库对象函数对应关系表。



下面以表的权限检查为例进行权限检查过程说明。表权限检查函数 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;    else        return ACLCHECK_NO_PRIV;}
复制代码


pg_class_aclcheck 函数有 4 个入参,其中 table_oid 用于表示待检查的表,roleid 用于表示待检查的用户或角色,mode 表示待检查的权限,此权限可以是一种权限也可以是多种权限的组合。第 4 个参数 check_nodegroup 用于表示是否检查 nodegroup 逻辑集群权限,如果调用时不给此参数赋值则默认为 true。函数返回值为枚举类型 AclResult,如果检查结果有权限返回 ACLCHECK_OK,无权限则返回 ACLCHECK_NO_PRIV。


pg_class_aclcheck 函数通过调用 pg_class_aclmask 函数实现对象权限检查。pg_class_aclmask 函数有 5 个参数,其中第 4 个参数 how 为 AclMaskHow 枚举类型,包括 ACLMASK_ALL 和 ACLMASK_ANY 两种取值;ACLMASK_ALL 表示需要满足待检查权限 mode 中的所有权限,ACLMASK_ANY 表示只需满足待检查权限 mode 中的一种权限即可。pg_class_aclmask 函数的其余 4 个参数 table_oid、roleid、mode 和 check_nodegroup,直接由 pg_class_aclcheck 函数传入。pg_class_aclmask 函数从 pg_class 系统表中获取 ACL 权限信息并调用 aclmask 函数完成权限位校验,通过 AclMode 数据类型返回权限检查结果。

用户头像

daydayup

关注

还未添加个人签名 2023-07-18 加入

还未添加个人简介

评论

发布
暂无评论
openGauss数据库源码解析系列文章——安全管理源码解析(四)_daydayup_InfoQ写作社区