在前面文章中介紹過“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時,則表示物件的所有權限,

openGauss將執行DML類操作和DDL類操作的權限分別記在兩個AclMode結構中,并以第15位的值來區分2者,從而實作對于每一個資料庫物件,相同的授權者和被授權者對應兩個不同的AclMode,分別表示記錄DML類操作權限和DDL類操作權限,實作方式如圖9-19和圖9-20所示,


每個權限引數代表的權限如表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所示,
資料結構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所示,

該函式的處理流程為:
(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所示,
物件 | 權限檢查 | 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 |
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 審計與追蹤”的相關內容的介紹,
敬請期待,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/323383.html
標籤:其他
