在 PostgreSQL中,建立安全的数据库连接是由用户标识和认证技术来共同实现,而建立连接后的安全访问保护则是基于角色的对象访问控制。
数据库里的每个对象所拥有的权限信息经常会发生变化,比如授予对象的部分操作权限给其他用户,或者删除用户在对象上的操作权限,亦或是对用户在对象上的操作权限进行更新等。以上操作在 SQL 中体现为 GRANT 和 REVOKE 语句,且都涉及对象权限信息的动态管理,即权限控制中的对象权限管理。
为了保护数据安全,当用户要对某个数据库对象进行操作之前,必须检查用户在对象上的操作权限,仅当用户对此对象拥有进行合法操作的权限时,才允许用户对此对象执行相应操作。上述操作检查的过程被称为对象权限检查。
ACL
访问控制列表(Access Control List,ACL)是对象权限管理和权限检查的基础,PostgreSQL 通过操作 ACL 实现对象的访问控制管理,在 PostgreSQL 中每个数据库对象都具有 ACL ,每个对象的 ACL 存储了此对象的所有授权信息。当用户访问对象时,只有它在对象的 ACL 中并且具有所需的权限时才能访问该对象。当用户要更新对象的权限时,只需要更新 ACL 上的权限信息即可。
ACL 是存储控制项(Access Control Entruy,ACE)的集合,组织结构如图(取自《PostgreSQL 数据库内核分析》):
每个 ACL 实际是一个由多个 AclItem 构成的链表。每个 AclItem 对应一个 ACE 。 ACE 中记录着可访问对象的用户或者执行单元,此外还记录了可在对象上进行权限操作的用户或者执行单元。PostgreSQL 中,ACE 由受权者、授权者以及权限位三部分组成。
/*
* AclItem
*
* Note: must be same size on all platforms, because the size is hardcoded
* in the pg_type.h entry for aclitem.
*/
typedef struct AclItem
{
Oid ai_grantee; /* ID that this item grants privs to */
Oid ai_grantor; /* grantor of privs */
AclMode ai_privs; /* privilege bits */
} AclItem;
其中,字段 ai_privs 是 AclMode 类型。AclMode 是一个 32 位的比特位,其高 16 位为权限选项位,低 16 位为该 ACE 中的操作位权限。每个操作权限占 1 个比特位,当该比特位的取值为 1 时,表示 ACE 中的 ai_grabtee 对应的用户(受权者)具有此对象的相应操作权限,否则,表示用户没有相应权限。AclMode 结构如下所示(取自《PostgreSQL 数据库内核分析》):
ace type:记录了 ACE 的被授权者(受权者)类型,可以是用户、组或者 public。
Grant options:记录了各权限位对应的授出或者被转授选项。
低 16 位:分别记录了各个权限位的授予情况,从低到高依次为 INSERT、SELECT、UPDATE、DELETE、TRUNCATE 等等。若当授予语句使用 ALL 时,则表示包含所有权限。
ACL 检查
调用 src/backend/catalog/aclchk.c
下的函数 pg_class_aclcheck
,获取用户在该表上的权限集,比较该权限集与操作所需权限集,若前者大于后者,则检查通过。
首先获取到对应的元组,从 pg_class 系统表中:
/*
* Must get the relation's tuple from pg_class
*/
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
if (!HeapTupleIsValid(tuple))
{
if (is_missing != NULL)
{
/* return "no privileges" instead of throwing an error */
*is_missing = true;
return 0;
}
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation with OID %u does not exist",
table_oid)));
}
classForm = (Form_pg_class) GETSTRUCT(tuple);
判断操作的表是否是系统表,如果是系统表则禁止操作。通过 IsSystemClass
函数判断其 relid 是否小于 FirstUnpinnedObjectId。
/*
* Deny anyone permission to update a system catalog unless
* pg_authid.rolsuper is set.
*
* As of 7.4 we have some updatable system views; those shouldn't be
* protected in this way. Assume the view rules can take care of
* themselves. ACL_USAGE is if we ever have system sequences.
*/
if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
IsSystemClass(table_oid, classForm) &&
classForm->relkind != RELKIND_VIEW &&
!superuser_arg(roleid))
mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE);
从 pg_class 系统表中取出操作表的 ACL ,产生一个副本
/*
* Normal case: get the relation's ACL from pg_class
*/
ownerId = classForm->relowner;
aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
&isNull);
if (isNull)
{
/* No ACL, so build default ACL */
switch (classForm->relkind)
{
case RELKIND_SEQUENCE:
acl = acldefault(OBJECT_SEQUENCE, ownerId