⭐opengauss数据库源码解析系列文章——对象权限管理⭐(代码片段)

Gauss松鼠会 Gauss松鼠会     2023-01-15     801

关键词:

❤️‍大家好,我是Gauss松鼠会,欢迎进来学习啦~❤️‍

在前面文章中介绍过“9.3 角色管理整”,本篇我们介绍第9章 安全管理源码解析中“9.4 对象权限管理”的相关精彩内容介绍。

9.4 对象权限管理

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

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

图9-18 openGauss AclMode结构图

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

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

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

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

参数

对象权限

参数

对象权限

a

INSERT

T

TEMPORARY

r

SELECT

c

CONNECT

w

UPDATE

p

COMPUTE

d

DELETE

R

READ

D

TRUNCATE

W

WRITE

x

REFERENCES

A

ALTER

t

TRIGGER

P

DROP

X

EXECUTE

m

COMMENT

U

USAGE

i

INDEX

C

CREATE

v

VACUUM

2. 对象权限管理

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

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

图9-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);


9.4.2 权限检查

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

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

对象

权限检查

ACL检查

所有者检查

系统表

table

pg_class_aclcheck

pg_class_aclmask

pg_class_ownercheck

pg_class

column

pg_attribute_aclcheck

pg_attribute_aclmask

NA

pg_attribute

database

pg_database_aclcheck

pg_database_aclmask

pg_database_ownercheck

pg_database

function

pg_proc_aclcheck

pg_proc_aclmask

pg_proc_ownercheck

pg_proc

language

pg_language_aclcheck

pg_language_aclmask

pg_language_ownercheck

pg_language

largeobject

pg_largeobject_aclcheck_snapshot

pg_largeobject_aclmask_snapshot

pg_largeobject_ownercheck

pg_largeobject_metadata

namespace

pg_namespace_aclcheck

pg_namespace_aclmask

pg_namespace_ownercheck

pg_namespace

tablespace

pg_tablespace_aclcheck

pg_tablespace_aclmask

pg_tablespace_ownercheck

pg_tablespace

foreign data wrapper

pg_foreign_data_wrapper_aclcheck

pg_foreign_data_wrapper_aclmask

pg_foreign_data_wrapper_ownercheck

pg_foreign_data_wrapper

foreign server

pg_foreign_server_aclcheck

pg_foreign_server_aclmask

pg_foreign_server_ownercheck

pg_foreign_server

type

pg_type_aclcheck

pg_type_aclmask

pg_type_ownercheck

pg_type

下面以表的权限检查为例进行权限检查过程说明。表权限检查函数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数据类型返回权限检查结果。

感谢大家学习第9章 安全管理源码解析中“9.4 对象权限管理”的精彩内容,下一篇我们开启“9.5 审计与追踪”的相关内容的介绍。
敬请期待。

💜走过路过,看到这里了,还请各位小伙伴点赞、收藏、评论,千言万语,不胜感谢💜

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...aceUpdate更新模式,中文意思为:原地更新,是openGauss内核新增的一种存储模式。openGauss内核 查看详情

⭐opengauss数据库源码解析系列文章——角色管理⭐(代码片段)

❤️‍大家好,我是Gauss松鼠会,欢迎进来学习啦~❤️‍在前面介绍过“9.1安全管理整体架构和代码概览、9.2安全认证”,本篇我们介绍第9章安全管理源码解析中“9.3角色管理”的相关精彩内容介绍。9.3角色管理角... 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...储引擎,针对众核和大内存服务器进行了优化。MOT是openGauss数据库的一个先进特性& 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...绍。4.2.5行存储索引机制本节以B-Tree索引为例,介绍openGauss中行存储(格式)表的索引机制。索引本质上是对数据的一种物理有序聚簇。有序聚簇参考的排序字段被称 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

上一篇我们讲述了“4.2磁盘引擎”中“4.2.1磁盘引擎整体框架及代码概览”与“4.2.2行存储统一访存接口”。本篇我们将讲述“4.2.3astore”。4.2.3astoreastore整体框架astore整体框架如图4-2所示。如上所述,作为行存储子格式之一&#... 查看详情

opengauss数据库源码解析系列文章——事务机制源码解析(代码片段)

上一篇为介绍完"5.1事务整体架构和代码概览"及“5.2事务并发控制”,本篇将继续介绍“5.3锁机制”的精彩内容。5.3锁机制数据库对公共资源的并发控制是通过锁来实现的,根据锁的用途不同,通常可以分为3种&... 查看详情

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

...查询优化”及“6.4小结”的相关内容的介绍。6.3查询优化openGauss数据库的查询优化过程功能比较明晰,从源代码组织的角度来看,相关代码分布在不同的目录下,如表6-6所示。表6-6查询优化模块说明模块目录说明查询... 查看详情

opengauss数据库源码解析系列文章——执行器解析(代码片段)

...线式的执行,对底层的存储引擎中的数据进行操作。openGauss数据库将执行的过程抽象成了不同类型的算子,同时结合编 查看详情

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

本篇我们开启“SQL引擎源解析”中“6.1概述”及“6.2SQL解析”的精彩内容介绍。第6章SQL引擎源解析SQL引擎作为数据库系统的入口,主要承担了对SQL语言进行解析、优化、生成执行计划的作用。对于用户输入的SQL语句,SQL... 查看详情

⭐opengauss数据库源码解析系列文章——deepsql⭐(代码片段)

...另外一个大方向,即DB4AI。在本章中,我们将介绍openGauss的 查看详情

opengauss数据库源码解析系列文章——执行器解析(代码片段)

上一篇介绍了第七章执行器解析中“7.4表达式计算”及“7.5编译执行”的相关内容,本篇将介绍“7.6向量化引擎”及“7.7小结”的精彩内容。7.6向量化引擎传统的行执行引擎大多采用一次一元组的执行模式,这样在执行... 查看详情

opengauss数据库源码解析系列文章——ai技术(代码片段)

上一篇介绍了第七章执行器解析中“7.6向量化引擎”及“7.7小结”的相关内容,本篇我们开启第八章AI技术中“8.1概述”及“8.2自调优”的相关精彩内容介绍。AI技术最早可以追溯到20世纪50年代,甚至比数据库系统的发展... 查看详情

⭐opengauss数据库源码解析系列文章——ai查询时间预测⭐(代码片段)

上一篇介绍了“8.5指标采集、预测与异常检测”的相关内容,本篇我们介绍“8.6AI查询时间预测”的相关精彩内容介绍。8.6AI查询时间预测在前面介绍过“慢SQL发现”特性,该特性的典型场景是新业务上线前的检查,... 查看详情

opengauss数据库源码解析系列文章——ai技术之“指标采集预测与异常检测”(代码片段)

上一篇介绍了“8.4智能索引推荐”的相关内容,本篇我们介绍“8.5指标采集、预测与异常检测”的相关精彩内容介绍。8.5指标采集、预测与异常检测数据库指标监控与异常检测技术,通过监控数据库指标,并基于时序... 查看详情

opengauss数据库源码解析系列文章——数据安全技术(上)(代码片段)

...理源码解析中“9.6数据安全技术”的相关精彩内容介绍。openGauss采用了多种加密解密技术来提升数据在各个环节的安全性。9.6.1数据加解密接口用户在使用数据库时,除了需要基本的数据库安全之外,还会对导入的数据进... 查看详情

数据库迁移系列从mysql到opengauss的数据库对象迁移实践(代码片段)

在之前这一篇中我们分享过使用chameleon工具完成MySQL到openGauss的全量数据复制、实时在线复制。9.30新发布的openGauss3.1.0版本,工具的全量迁移和增量迁移的性能不但有了全面提升,而且支持数据库对象视图、触发器、自定... 查看详情

tensorflow源码解析系列文章索引

文章索引framework解析resourceallocatortensoropnodekernelgraphdevicefunctionshape_inferencecommon_runtime解析devicesessiongraph_optimizerexecutor-1executor-2direct_session后记关于起源阅读tensorflow源码时,为了敦促自己主动思考,把阅读的笔 查看详情

数据库迁移系列从oracle迁移到opengauss实战分享(代码片段)

之前的迁移系列中我们介绍了Mysql到openGauss的迁移方法,本篇介绍使用Ora2og工具从Oracle到openGauss数据库的迁移。文章目录简介迁移前准备环境软件安装ora2og工具安装创建迁移项目配置ora2pg.conf测试迁移导出导入Ora2Pg不足FAQ简介... 查看详情