postgresql acl权限检查

acl 权限检查

简述

当一个对象被创建时,它会被分配一个所有者。所有者通常是执行创建语句的角色。对于大多数类型的对象,初始状态是只有所有者(或超级用户)才能对该对象进行任何操作。要允许其他角色使用它,必须授予权限

有不同种类的权限:SELECTINSERTUPDATEDELETETRUNCATEREFERENCESTRIGGERCREATECONNECTTEMPORARYEXECUTEUSAGESETALTER SYSTEMMAINTAIN(pg18)。适用于特定对象的权限取决于对象的类型(表、函数等)。

修改或销毁对象的权利是对象所有者固有的,不能单独授予或撤销

授予或取消权限

sql 复制代码
-- 可以改变对象的owner,使另一个用户拥有该对象的所有权限
ALTER TABLE table_name OWNER TO new_owner;
-- grant 授权
GRANT UPDATE ON accounts TO joe;
-- revoke 撤销权限
-- 特殊的"角色"名称 PUBLIC 可用于将权限授予系统上的每个角色, 另外还可以创建用户组来使组内用户都有对应的权限
REVOKE ALL ON accounts FROM PUBLIC;

对象可授予的权限

不同的对象,可以被分配的权限不同,比如对象位数据库的话就没有select权限,select权限应该分配给表类的对象

权限 缩写 适用的对象类型
SELECT r ("读取") LARGE OBJECTSEQUENCETABLE(及类表对象)、表列
INSERT a ("追加") TABLE、表列
UPDATE w ("写入") LARGE OBJECTSEQUENCETABLE、表列
DELETE d TABLE
TRUNCATE D TABLE
REFERENCES x TABLE、表列
TRIGGER t TABLE
CREATE C DATABASESCHEMATABLESPACE
CONNECT c DATABASE
TEMPORARY T DATABASE
EXECUTE X FUNCTIONPROCEDURE
USAGE U DOMAINFOREIGN DATA WRAPPERFOREIGN SERVERLANGUAGESCHEMASEQUENCETYPE
SET s PARAMETER
ALTER SYSTEM 一个 PARAMETER
MAINTAIN m TABLE
对象类型 所有权限 默认 PUBLIC 权限 psql 命令
DATABASE CTc Tc \l
DOMAIN U U \dD+
FUNCTIONPROCEDURE X X \df+
FOREIGN DATA WRAPPER U none \dew+
FOREIGN SERVER U none \des+
LANGUAGE U U \dL+
LARGE OBJECT rw none \dl+
PARAMETER sA none \dconfig+
SCHEMA UC none \dn+
SEQUENCE rwU none \dp
TABLE(及类表对象) arwdDxtm none \dp
Table column arwx none \dp
TABLESPACE C none \db+
TYPE U U \dT+
c 复制代码
// 对象所有的权限
#define ACL_ALL_RIGHTS_COLUMN		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
#define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
#define ACL_ALL_RIGHTS_SEQUENCE		(ACL_USAGE|ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_DATABASE		(ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW			(ACL_USAGE)
#define ACL_ALL_RIGHTS_FOREIGN_SERVER (ACL_USAGE)
#define ACL_ALL_RIGHTS_FUNCTION		(ACL_EXECUTE)
#define ACL_ALL_RIGHTS_LANGUAGE		(ACL_USAGE)
#define ACL_ALL_RIGHTS_LARGEOBJECT	(ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM)
#define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)

代码实现

每个对象的元数据系统表中存放了AclItem的数组, 用来存放grant的权限,AclItem是一个带有grantee id,grantor id,以及AclMode(uint64) 类型的的ai_privs,ai_privs存放grantor 授权给grantee 的权限

用户执行sql,在对对象访问时(一般在实际执行操作前,初始化阶段即判断是否有权限),构建需要的权限,组成同样的AclMode 类型的权限,然后查系统表,找到对应的对象,遍历AclItem数组,找到grantee为自身的item,然后按位与,即可知道当前是否满足执行sql需要的权限

grant和revoke是获取对应的对象元组,然后修改元组对应grantee的aclmode值

c 复制代码
CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,RelationRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
	//....
#ifdef CATALOG_VARLEN			/* variable-length fields start here */
	// 每个对象的元数据系统表中存放了AclItem的数组, 用来存放grant的权限
	aclitem		relacl[1] BKI_DEFAULT(_null_);
    // ....
#endif
} FormData_pg_class;

// 实际在磁盘中存放的结构体ArrayType
// acl是一个一维的存放AclItem结构体的数组
typedef struct ArrayType Acl;

typedef struct AclItem
{
	Oid			ai_grantee;		//权限被授予者
	Oid			ai_grantor;		//权限授予者
	AclMode		ai_privs;		/* privilege bits */
} AclItem;

typedef uint64 AclMode;			/* a bitmask of privilege bits */

// 权限对应的bit位
#define ACL_INSERT		(1<<0)	/* for relations */
#define ACL_SELECT		(1<<1)
#define ACL_UPDATE		(1<<2)
#define ACL_DELETE		(1<<3)
#define ACL_TRUNCATE	(1<<4)
#define ACL_REFERENCES	(1<<5)
#define ACL_TRIGGER		(1<<6)
#define ACL_EXECUTE		(1<<7)	/* for functions */
#define ACL_USAGE		(1<<8)	/* for various object types */
#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
#define ACL_CREATE_TEMP (1<<10) /* for databases */
#define ACL_CONNECT		(1<<11) /* for databases */
#define ACL_SET			(1<<12) /* for configuration parameters */
#define ACL_ALTER_SYSTEM (1<<13)	/* for configuration parameters */
#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
#define ACL_NO_RIGHTS	0
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE

// AclMode 是一个64位的无符号整数,前32位用来表示当前可以授予给别人的权限, 后32位表示当前可用的权限
#define ACLITEM_GET_PRIVS(item)    ((item).ai_privs & 0xFFFFFFFF)
#define ACLITEM_GET_GOPTIONS(item) (((item).ai_privs >> 32) & 0xFFFFFFFF)
#define ACLITEM_GET_RIGHTS(item)   ((item).ai_privs)

// 如 ACL_INSERT & ACLITEM_GET_PRIVS(item) !=0 表示当前可以执行insert权限
// 如 ACL_INSERT & ACLITEM_GET_GOPTIONS(item) !=0 表示当前可以授予别人insert权限

权限检查函数

c 复制代码
// 主要在grant/revoke时调用
static AclMode
pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
		   AclMode mask, AclMaskHow how)
{
	switch (objtype)
	{
		case OBJECT_COLUMN:
			return
				pg_class_aclmask(object_oid, roleid, mask, how) |
				pg_attribute_aclmask(object_oid, attnum, roleid, mask, how);
		case OBJECT_TABLE:
		case OBJECT_SEQUENCE:
			return pg_class_aclmask(object_oid, roleid, mask, how);
		case OBJECT_LARGEOBJECT:
			return pg_largeobject_aclmask_snapshot(object_oid, roleid,
												   mask, how, NULL);
		case OBJECT_PARAMETER_ACL:
			return pg_parameter_acl_aclmask(object_oid, roleid, mask, how);
		case OBJECT_STATISTIC_EXT:
			elog(ERROR, "grantable rights not supported for statistics objects");
			/* not reached, but keep compiler quiet */
			return ACL_NO_RIGHTS;
        case OBJECT_EVENT_TRIGGER:
			elog(ERROR, "grantable rights not supported for event triggers");
			/* not reached, but keep compiler quiet */
			return ACL_NO_RIGHTS;
         // 。。。 其他都调用object_aclmask
		case OBJECT_TYPE:
			return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
		default:
			elog(ERROR, "unrecognized object type: %d",
				 (int) objtype);
			/* not reached, but keep compiler quiet */
			return ACL_NO_RIGHTS;
	}
}

// 权限检查比较通用的函数
AclResult
object_aclcheck(Oid classid, Oid objectid, Oid roleid, AclMode mode)
{
	if (object_aclmask(classid, objectid, roleid, mode, ACLMASK_ANY) != 0)
		return ACLCHECK_OK;
	else
		return ACLCHECK_NO_PRIV;
}

static AclMode
object_aclmask(Oid classid, Oid objectid, Oid roleid,
			   AclMode mask, AclMaskHow how)
{
	int			cacheid;
	AclMode		result;
	HeapTuple	tuple;
	Datum		aclDatum;
	bool		isNull;
	Acl		   *acl;
	Oid			ownerId;

	/* Special cases */
	switch (classid)
	{
		case NamespaceRelationId:
			return pg_namespace_aclmask(objectid, roleid, mask, how);
		case TypeRelationId:
			return pg_type_aclmask(objectid, roleid, mask, how);
	}

	/* Even more special cases */
    // RelationRelationId 需要判断对应的objectid是否为系统表、是否为视图等
	Assert(classid != RelationRelationId);	/* should use pg_class_acl* */
	Assert(classid != LargeObjectMetadataRelationId);	/* should use
														 * pg_largeobject_acl* */

	// 超级用户跳过检查
	if (superuser_arg(roleid))
		return mask;

	// 获取cacheid, 在syscache中查询对应objectid的tuple,获取ownerid和aclDatum
	cacheid = get_object_catcache_oid(classid);

	tuple = SearchSysCache1(cacheid, ObjectIdGetDatum(objectid));
	if (!HeapTupleIsValid(tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_DATABASE),
				 errmsg("%s with OID %u does not exist", get_object_class_descr(classid), objectid)));

	ownerId = DatumGetObjectId(SysCacheGetAttrNotNull(cacheid,
													  tuple,
													  get_object_attnum_owner(classid)));

	aclDatum = SysCacheGetAttr(cacheid, tuple, get_object_attnum_acl(classid),
							   &isNull);
    // aclDatum为NULL,创建默认acl,否则获取acl数组
	if (isNull)
	{
		/* No ACL, so build default ACL */
		acl = acldefault(get_object_type(classid, objectid), ownerId);
		aclDatum = (Datum) 0;
	}
	else
	{
		/* detoast ACL if necessary */
		acl = DatumGetAclP(aclDatum);
	}
	// mask为需要的权限,acl为当前tuple上分配的权限,& 后获取结果
	result = aclmask(acl, roleid, ownerId, mask, how);

	// 释放资源返回
	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
		pfree(acl);

	ReleaseSysCache(tuple);

	return result;
}

typedef enum
{
	ACLMASK_ALL, 	//mask全匹配时即返回
	ACLMASK_ANY		//存在任意非0权限即返回
} AclMaskHow;

AclMode
aclmask(const Acl *acl, Oid roleid, Oid ownerId,
		AclMode mask, AclMaskHow how)
{
	AclMode		result;
	AclMode		remaining;
	AclItem    *aidat;
	int			i,
				num;
	if (acl == NULL)
		elog(ERROR, "null ACL");
	// 检查是否为1维数组,数组元素是否为aclitem
	check_acl(acl);

	/* Quick exit for mask == 0 */
	if (mask == 0)
		return 0;

	result = 0;

	// grantbit不为空 如果role有owner的权限(为owner或者继承自owner)
	if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
		has_privs_of_role(roleid, ownerId))
	{
		result = mask & ACLITEM_ALL_GOPTION_BITS;
		if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
			return result;
	}

	num = ACL_NUM(acl);
	aidat = ACL_DAT(acl);

	// 从数组中读aclitem, 检查grantee是否为 public或者自己, 然后通过ai_privs & mask ,得出结果
	for (i = 0; i < num; i++)
	{
		AclItem    *aidata = &aidat[i];

		if (aidata->ai_grantee == ACL_ID_PUBLIC ||
			aidata->ai_grantee == roleid)
		{
			result |= aidata->ai_privs & mask;
			// all mask全匹配时即返回, any result!=0即返回
			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
				return result;
		}
	}

	// 检查被roleid继承的其他用户是否有对应的权限
	remaining = mask & ~result;
	for (i = 0; i < num; i++)
	{
		AclItem    *aidata = &aidat[i];

		if (aidata->ai_grantee == ACL_ID_PUBLIC ||
			aidata->ai_grantee == roleid)
			continue;			/* already checked it */

		if ((aidata->ai_privs & remaining) &&
			has_privs_of_role(roleid, aidata->ai_grantee))
		{
			result |= aidata->ai_privs & mask;
			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
				return result;
			remaining = mask & ~result;
		}
	}

	return result;
}
c 复制代码
// 大部分对象调用object_aclcheck即可, 部分对象依然需要调用对应的检查函数
// 检查列对象是否有权限
pg_attribute_aclcheck
// 检查表对象是否有权限
pg_class_aclcheck   
// 检查GUC对象是否有权限  
pg_parameter_aclcheck
//  检查largeobject对象是否有权限  
pg_largeobject_aclcheck_snapshot    

grant revoke时的权限检查

grant

grant和revoke需要修改tuple上的acl数组,找到对应的grantee和grantor,修改权限aclmode

c 复制代码
void
select_best_grantor(Oid roleId, AclMode privileges,
					const Acl *acl, Oid ownerId,
					Oid *grantorId, AclMode *grantOptions)
{
	AclMode		needed_goptions = ACL_GRANT_OPTION_FOR(privileges);
	List	   *roles_list;
	int			nrights;
	ListCell   *l;

	// owner或superuser 即为最优 grantor
	if (roleId == ownerId || has_superuser_privilege_arg(roleId))
	{
		*grantorId = ownerId;
		*grantOptions = needed_goptions;
		return;
	}

	// 获取roleId的被继承role
	roles_list = roles_is_member_of(roleId的被继承role, ROLERECURSE_PRIVS,
									InvalidOid, NULL);

	/* initialize candidate result as default */
	*grantorId = roleId;
	*grantOptions = ACL_NO_RIGHTS;
	nrights = 0;
	// 选择有直接权限满足或者有最多的role作为最佳grantor
	foreach(l, roles_list)
	{
		Oid			otherrole = lfirst_oid(l);
		AclMode		otherprivs;

		otherprivs = aclmask_direct(acl, otherrole, ownerId,
									needed_goptions, ACLMASK_ALL);
		if (otherprivs == needed_goptions)
		{
			/* Found a suitable grantor */
			*grantorId = otherrole;
			*grantOptions = otherprivs;
			return;
		}

		/*
		 * If it has just some of the needed privileges, remember best
		 * candidate.
		 */
		if (otherprivs != ACL_NO_RIGHTS)
		{
			int			nnewrights = count_one_bits(otherprivs);

			if (nnewrights > nrights)
			{
				*grantorId = otherrole;
				*grantOptions = otherprivs;
				nrights = nnewrights;
			}
		}
	}
}
c 复制代码
// revoke和grant逻辑类似,都调用同一个接口ExecuteGrantStmt
ExecuteGrantStmt
    // ... name转oid、获取grant的对象所有可授予的所有权限等
    ExecGrantStmt_oids
    	// sequence或relation
    	ExecGrant_Relation
    		// 1 .先做检查,判断表/sequence是否存在,是否授予的权限属于表/sequence的权限
    		// 2. 获取旧的acl,没有的话调用acldefault 获取默认权限
    		select_best_grantor // 选择best_grantor 以及可授予的权限
    		restrict_and_check_grant	// 权限检查
    		merge_acl_with_grant
    			aclupdate			//更新数组中的值
    		heap_modify_tuple		// 修改元组
    		CatalogTupleUpdate
    		updateAclDependencies	// 更新依赖
    		expand_col_privileges	// 如果要对列授权,修改对应属性权限
    		ExecGrant_Attribute
    	ExecGrant_common
    	//和上面类似,没有对列授权的步骤
    	ExecGrant_Largeobject
    	ExecGrant_Parameter

sql语句执行时的权限检查

ddl检查

standard_ProcessUtility内对应操作执行前做检查

c 复制代码
// 创建表
DefineRelation
    RangeVarGetAndCheckCreationNamespace
    	object_aclcheck	//需要对应ns下的create权限
		object_aclcheck	//指定tablespace时,需要tablespace的create权限
    	object_aclcheck //ofType 需要usage权限
c 复制代码
// set guc
ExecSetVariableStmt
    set_config_option
		set_config_option_ext
			pg_parameter_aclcheck

。。。。

非utility语句检查
  1. 执行器初始化时对目标表做权限检查、对要调用的表达式做权限检查等
c 复制代码
initplan
    ExecCheckPermissions
    	ExecCheckOneRelPerms
    		pg_class_aclmask
    		// 所需权限都满足的话,即满足权限执行完毕返回,否则先判断是只含ACL_SELECT/ACL_INSERT/ACL_UPDATE的权限,然后进行列级检查
    		// ACL_SELECT时判断所读的列是否满足条件
    		pg_attribute_aclcheck_all // 从pg_attribute表中读出所有列,判断是否满足acl权限
    		// 或者pg_attribute_aclcheck对对应列做检查
    		// insert或update, 判断插入或删除的列是否满足条件
    		ExecCheckPermissionsModified
    			pg_attribute_aclcheck_all // 或者pg_attribute_aclcheck
    		
  1. 优化器统计信息场景使用

    复制代码
    examine_variable
    	all_rows_selectable
  2. 表达式初始化时,判断对应表达式函数是否有执行权限

c 复制代码
ExecInitExprRec
	ExecInitFunc
		object_aclcheck

。。。。

权限检查应尽量在初始化阶段做,主要调用函数object_aclcheck、pg_class_aclmask、pg_attribute_aclcheck_all、pg_attribute_aclcheck等

参考

PostgreSQL: 文档: 18: 5.8. 权限 - PostgreSQL 数据库

相关推荐
2401_874732531 小时前
构建一个桌面版的天气预报应用
jvm·数据库·python
bearpping1 小时前
Spring Boot 整合 MyBatis 与 PostgreSQL 实战指南
spring boot·postgresql·mybatis
码云数智-园园1 小时前
坚如磐石:数据库事务ACID特性的实现奥秘
数据库·oracle
十月南城1 小时前
文档化与知识库方法——ADR、Runbook与故障手册的结构与维护节奏
大数据·数据库
qq_417695051 小时前
实战:用Python开发一个简单的区块链
jvm·数据库·python
悲伤小伞2 小时前
9-MySQL_索引
linux·数据库·c++·mysql·centos
霖霖总总2 小时前
[Redis小技巧24]Redis主从复制深度解剖:不只是SLAVEOF,Redis主从复制背后的RunID、Backlog
数据库·redis
不吃香菜学java2 小时前
苍穹外卖-菜品分页查询
数据库·spring boot·tomcat·log4j·maven·mybatis
狼与自由2 小时前
Redis 分布式锁
数据库·redis·分布式
skiy2 小时前
redis 使用
数据库·redis·缓存