PostgreSQL行级安全策略探究

news2024/9/24 1:25:38

前言

最近和朋友讨论oracle行级安全策略(VPD)时,查看了下官方文档,看起来VPD的原理是针对应用了Oracle行级安全策略的表、视图或同义词发出的 SQL 语句动态添加where子句。通俗理解就是将行级安全策略动态添加为where 条件。那么PG中的行级安全策略是怎么处理的呢?

行级安全简介

行级安全策略(Row Level Security)是更细粒度的数据安全控制策略。行级策略可以根据每个用户限制哪些行可以通过常规查询返回,哪些行可以通过数据修改命令插入、更新或删除。默认情况下,表没有任何行级安全策略,因此如果用户根据 SQL 权限系统具有表的访问权限,则其中的所有行都可以平等地用于查询或更新。

在PG中我们可以创建行级策略,在SQL执行时行级策略表达式将作为查询的一部分运行。
https://www.postgresql.org/docs/16/ddl-rowsecurity.html

行级安全演示

创建3个用户

postgres=# create user admin;
CREATE ROLE
postgres=# create user peter;
CREATE ROLE
postgres=# create user bob;
CREATE ROLE

创建一个rlsdb数据库

postgres=# create database rlsdb owner admin;
CREATE DATABASE

在rlsdb中使用admin用户创建表employee,并插入3个用户对应的数据

postgres=# \c rlsdb admin
You are now connected to database "rlsdb" as user "admin".
rlsdb=> create table employee ( empno int, ename text, address text, salary int, account_number text );
CREATE TABLE
rlsdb=> insert into employee values (1, 'admin', '2 down str',  80000, 'no0001' );
INSERT 0 1
rlsdb=> insert into employee values (2, 'peter', '132 south avn',  60000, 'no0002' );
INSERT 0 1
rlsdb=> insert into employee values (3, 'bob', 'Down st 17th',  60000, 'no0003' );
INSERT 0 1
rlsdb=> 

授权后,三个用户都能看到employee表的所有数据

rlsdb=> grant select on table employee to peter;
GRANT
rlsdb=> grant select on table employee to bob;
GRANT
rlsdb=> select * from employee;
 empno | ename |    address    | salary | account_number 
-------+-------+---------------+--------+----------------
     1 | admin | 2 down str    |  80000 | no0001
     2 | peter | 132 south avn |  60000 | no0002
     3 | bob   | Down st 17th  |  60000 | no0003
(3 rows)

rlsdb=> 
rlsdb=> \c rlsdb peter
You are now connected to database "rlsdb" as user "peter".
rlsdb=> select * from employee;
 empno | ename |    address    | salary | account_number 
-------+-------+---------------+--------+----------------
     1 | admin | 2 down str    |  80000 | no0001
     2 | peter | 132 south avn |  60000 | no0002
     3 | bob   | Down st 17th  |  60000 | no0003
(3 rows)

rlsdb=> \c rlsdb bob
You are now connected to database "rlsdb" as user "bob".
rlsdb=> select * from employee;
 empno | ename |    address    | salary | account_number 
-------+-------+---------------+--------+----------------
     1 | admin | 2 down str    |  80000 | no0001
     2 | peter | 132 south avn |  60000 | no0002
     3 | bob   | Down st 17th  |  60000 | no0003
(3 rows)

使用admin用户创建行级安全策略,对于peter和bob就只能看到自己的数据了。

rlsdb=> \c rlsdb admin
You are now connected to database "rlsdb" as user "admin".
rlsdb=> CREATE POLICY emp_rls_policy ON employee FOR ALL TO PUBLIC USING (ename=current_user);
CREATE POLICY
rlsdb=> ALTER TABLE employee ENABLE ROW LEVEL SECURITY;
ALTER TABLE
rlsdb=> \c rlsdb peter
You are now connected to database "rlsdb" as user "peter".
rlsdb=> select * from employee;
 empno | ename |    address    | salary | account_number 
-------+-------+---------------+--------+----------------
     2 | peter | 132 south avn |  60000 | no0002
(1 row)

rlsdb=> \c rlsdb bob
You are now connected to database "rlsdb" as user "bob".
rlsdb=> select * from employee;
 empno | ename |   address    | salary | account_number 
-------+-------+--------------+--------+----------------
     3 | bob   | Down st 17th |  60000 | no0003
(1 row)

rlsdb=>

行级安全原理

先看下行级安全策略在数据库中的呈现是什么样的。
查看pg_policy表,可以看到我们创建的emp_rls_policy这个策略,具体的策略polqual是一串字符,熟悉parsetree结构的朋友能关注到这是一个OPEXPR node。我们常见的where 条件也是类似的结构。

我们可以使用函数让polqual以更适合人阅读的方式来展示。

创建策略时,其实是将策略转换为where子句存到pg_policy表中。

ObjectAddress
CreatePolicy(CreatePolicyStmt *stmt)
{
/*省略部分代码行*/
/*将策略转化为where子句*/
	qual = transformWhereClause(qual_pstate,
								stmt->qual,
								EXPR_KIND_POLICY,
								"POLICY");

	with_check_qual = transformWhereClause(with_check_pstate,
										   stmt->with_check,
										   EXPR_KIND_POLICY,
										   "POLICY");

	/* Fix up collation information */
	assign_expr_collations(qual_pstate, qual);
	assign_expr_collations(with_check_pstate, with_check_qual);
/* 将转换后的子句写入pg_policy*/
	/* Open pg_policy catalog */
	pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock);

	/* Set key - policy's relation id. */
	ScanKeyInit(&skey[0],
				Anum_pg_policy_polrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(table_id));

	/* Set key - policy's name. */
	ScanKeyInit(&skey[1],
				Anum_pg_policy_polname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(stmt->policy_name));

	sscan = systable_beginscan(pg_policy_rel,
							   PolicyPolrelidPolnameIndexId, true, NULL, 2,
							   skey);

	policy_tuple = systable_getnext(sscan);

	/* Complain if the policy name already exists for the table */
	if (HeapTupleIsValid(policy_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("policy \"%s\" for table \"%s\" already exists",
						stmt->policy_name, RelationGetRelationName(target_table))));

	policy_id = GetNewOidWithIndex(pg_policy_rel, PolicyOidIndexId,
								   Anum_pg_policy_oid);
	values[Anum_pg_policy_oid - 1] = ObjectIdGetDatum(policy_id);
	values[Anum_pg_policy_polrelid - 1] = ObjectIdGetDatum(table_id);
	values[Anum_pg_policy_polname - 1] = DirectFunctionCall1(namein,
															 CStringGetDatum(stmt->policy_name));
	values[Anum_pg_policy_polcmd - 1] = CharGetDatum(polcmd);
	values[Anum_pg_policy_polpermissive - 1] = BoolGetDatum(stmt->permissive);
	values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids);

	/* Add qual if present. */
	if (qual)
		values[Anum_pg_policy_polqual - 1] = CStringGetTextDatum(nodeToString(qual));
	else
		isnull[Anum_pg_policy_polqual - 1] = true;

	/* Add WITH CHECK qual if present */
	if (with_check_qual)
		values[Anum_pg_policy_polwithcheck - 1] = CStringGetTextDatum(nodeToString(with_check_qual));
	else
		isnull[Anum_pg_policy_polwithcheck - 1] = true;

	policy_tuple = heap_form_tuple(RelationGetDescr(pg_policy_rel), values,
								   isnull);

	CatalogTupleInsert(pg_policy_rel, policy_tuple);

	/* Record Dependencies */
	target.classId = RelationRelationId;
	target.objectId = table_id;
	target.objectSubId = 0;

	myself.classId = PolicyRelationId;
	myself.objectId = policy_id;
	myself.objectSubId = 0;

	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);

	recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
						   DEPENDENCY_NORMAL);

	recordDependencyOnExpr(&myself, with_check_qual,
						   with_check_pstate->p_rtable, DEPENDENCY_NORMAL);

	/* Register role dependencies */
	target.classId = AuthIdRelationId;
	target.objectSubId = 0;
	for (i = 0; i < nitems; i++)
	{
		target.objectId = DatumGetObjectId(role_oids[i]);
		/* no dependency if public */
		if (target.objectId != ACL_ID_PUBLIC)
			recordSharedDependencyOn(&myself, &target,
									 SHARED_DEPENDENCY_POLICY);
	}

	InvokeObjectPostCreateHook(PolicyRelationId, policy_id, 0);

	/* Invalidate Relation Cache */
	CacheInvalidateRelcache(target_table);

	/* Clean up. */
	heap_freetuple(policy_tuple);
	free_parsestate(qual_pstate);
	free_parsestate(with_check_pstate);
	systable_endscan(sscan);
	relation_close(target_table, NoLock);
	table_close(pg_policy_rel, RowExclusiveLock);

	return myself;
}

在SQL执行时,查询重写阶段会将对应的安全策略拼接到parsetree里,最后生成执行计划去执行。
从执行计划来看SQL没有where条件,但是执行计划中存在 Filter: (ename = CURRENT_USER),证明了这个过程。

rlsdb=> explain analyze select * from employee ;
                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Seq Scan on employee  (cost=0.00..19.15 rows=3 width=104) (actual time=0.010..0.012 rows=1 loops=1)
   Filter: (ename = CURRENT_USER)
   Rows Removed by Filter: 2
 Planning Time: 0.416 ms
 Execution Time: 0.036 ms
(5 rows)

rlsdb=>

再debug验证下这个过程。
给fireRIRrules函数设置断点,进入断点后从stack可以看到目前是在QueryRewrite阶段,结合一些规则进行查询重写。

观察这个时候的parsetree,可以看到还没有将安全策略对应的OPEXPR拼接进来。

等执行到get_row_security_policies函数已获取到表对应安全策略securityQuals。
打印securityQuals可以看到和我们查询pg_policy中的OPEXPR是一致的。

接着将securityQuals加入到rte的list中,这样我们再去打印parsetree就可以看到安全策略securityQuals对应的OPEXPR已经被拼接进来。

然后就是去生成执行计划并执行。

小结

PG的RLS也是将对应的策略动态转换为where子句,在查询重写阶段将安全策略拼接到parsetree,生成执行计划去执行。

行级安全策略,可以提供更精细粒度的表数据权限管理,在一定的场景下,比如只让用户看到自己对应的数据,能做到更安全的权限把控。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1921054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】信号量和线程池

目录 一、POSIX信号量 二、基于环形队列和信号量的生产消费模型 三、线程池 一、POSIX信号量 POSIX信号量&#xff08;POSIX Semaphores&#xff09;是一种进程间或线程间同步机制&#xff0c;它允许进程或线程以协调的方式访问共享资源或进行其他形式的同步。与System V信…

用MATLAB绘制三向应力圆

% 定义主应力值 sigma1 100; % MPa sigma2 50; % MPa sigma3 -33; % MPa sigma_m1(sigma1 sigma3)/2; sigma_m2(sigma1 sigma2)/2; sigma_m3(sigma2 sigma3)/2; % 计算半径 r1 (sigma1 - sigma3) / 2; r2 (sigma1 - sigma2) / 2; r3 (sigma2 - sigma3…

2024年16个适合现代应用程序的最佳API网关

什么是API&#xff1f; API是一个软件解决方案&#xff0c;作为中介&#xff0c;使两个应用程序能够相互交互。以下一些特征让API变得更加有用和有价值&#xff1a; 遵守REST和HTTP等易于访问、广泛理解和开发人员友好的标准。API不仅仅是几行代码&#xff1b;这些是为移动开…

生成式AI (Generative artificial intelligence, GenAI or GAI)

安利一个新加坡南洋理工大学的论文总结The Age of Generative AI 一、什么是生成式AI Generative AI, sometimes called gen AI, is artificial intelligence (AI) that can create original content—such as text, images, video, audio or software code—in response to a …

AIGC降痕指南:如何让AI写作不留痕迹

随着AI技术的飞速发展&#xff0c;AI论文工具正逐渐成为学术界的新宠。它们以高效、便捷的优势&#xff0c;吸引了众多学者的目光。然而&#xff0c;随之而来的学术诚信与原创性问题&#xff0c;也成为人们关注的焦点。 如何在享受AI带来的便利的同时&#xff0c;确保论文的原…

凯泽斯劳滕理工大学通过TS-AWG全新DDS固件选件加速量子计算机开发

凯泽斯劳滕理工大学&#xff08;Technische Universitt Kaiserslautern&#xff09;&#xff0c;位于德国莱茵兰-普法尔茨州&#xff0c;是一所国立理工科大学。该大学成立于1970年7月13日&#xff0c;最初是特里尔/凯泽斯劳滕兄弟大学的一部分。1975年&#xff0c;凯泽斯劳滕理…

2025~《数据结构》试题~考研

作者主页: 知孤云出岫 目录 数据结构模拟卷一、选择题&#xff08;每题2分&#xff0c;共20分&#xff09;二、填空题&#xff08;每题3分&#xff0c;共15分&#xff09;三、简答题&#xff08;每题10分&#xff0c;共30分&#xff09;四、编程题&#xff08;每题15分&#x…

Flutter跨平台开发技术

仅分享文字&#xff0c;见谅 Flutter Flutter 介绍 功能跨平台性架构流行度Flutter vs React Native 配置 Windows Flutter App 环境配置 Tizen Flutter App 环境用 Dart 语言开发 Flutter AppFlutter-Tizen 的限制 Flutter 介绍 Flutter 是由 Google 推出的开源移动应用开发…

zabbix web页面添加对nginx监控

1.nginx安装zabbix-agent2,并修改配置文件中server地址为zabbix-server的地址 ]# egrep ^Server|^Hostname /etc/zabbix/zabbix_agent2.conf Server172.16.1.162 ServerActive172.16.1.162 Hostnameweb01 2.zabbix web页面上进行添加客户端 3.默认的nginx监控模板中的状态模块…

C++心决之stl中那些你不知道的秘密(string篇)

目录 1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 2. 标准库中的string类 2.1 string类 2.2 string类的常用接口说明 1. string类对象的常见构造 2. string类对象的操作 3.vs和g下string结构的说明 3. string类的模拟实现 3.2 浅拷贝 3.3 深拷贝 3.4 写…

【算法】【二分法】二分法详解

先给y总打一个广告。&#xff08;我这种废物收不到钱&#xff09; 本科时候就在打蓝桥杯玩玩算法&#xff0c;当时听朋友的一个刷题且涵盖教程的网站&#xff0c;ACWING。 www.acwing.com 里面好处是大部分基础算法都有&#xff0c;Y总的视频&#xff01; y总我的神&#xff01…

设计模式——适配器设计模式

设计模式——适配器设计模式 适配器设计模式1.1 基本介绍1.2 工作原理1.3 类适配器模式1.3.1 基本介绍1.3.2 示例1.3.3 代码实现1.3.4 注意事项 1.4 对象适配器模式1.4.1 基本介绍1.4.2 示例1.4.3 代码实现1.4.4 注意事项 1.5 接口适配器模式1.5.1 基本介绍1.5.2 示例1.5.3 代码…

Web3 社交领域的开发技术

Web3 社交领域的开发技术主要包括以下几种&#xff0c;随着 Web3 技术的不断发展&#xff0c;Web3 社交领域将会出现更多新的技术和应用场景。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 区块链技术 区块链技术是 Web3 社交的…

在Rstudio中点一点就出来了一个R包

新建一个Package Build一个Package 更多开发指南 https://r-pkgs.org/

vscode使用及调试方式和技巧

常用快捷键 ctrl ~ 显示隐藏终端面板 Ctrl\ 快速拆分文件编辑 Alt ↑↓ 移动当前代码行的位置 CtrlD 选中当前匹配项 CtrlB 切换侧边栏 alt 单机左键 或 长按鼠标滚轮鼠标左键下拉 添加多处光标 Ctrlp 快捷键设置 vscode调试 2022年了&#xff0c;该学会用VSC…

通用详情页的打造

背景介绍 大家都知道&#xff0c;详情页承载了站内的核心流量。它的量级到底有多大呢&#xff1f; 我们来看一下&#xff0c;日均播放次数数亿次&#xff0c;这么大的流量&#xff0c;其重要程度可想而知。 在这样一个页面&#xff0c;每一个功能都是大量业务的汇总点。 作为…

RayLink企业版正式上线!

哈咯大家~我是小R 经过RayLink团队的努力&#xff0c;大家期待的RayLink企业版正式上线了&#xff0c;相对于传统的远程控制软件&#xff0c;企业版本更能满足对于企业的安全性&#xff0c;扩展性&#xff0c;以来满足企业不断变化的业务需求。 RayLink企业版&#xff1a;一站…

Android C++系列:Linux网络(二)通信过程

上图对应两台计算机在同一网段中的情况,如果两台计算机在不同的网段中,那么数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器,如下图所示其实在链路层之下还有物理层,指的是电信号的传递方式,比如现在以太网通用的网线 (双绞线)、早期以太网采用的的同轴电…

逆向之在浏览器上对window等对象进行hook

一般情况下&#xff0c;在chrome浏览器上使用JS对window document等对象是无法hook的&#xff0c;除非魔改浏览器底层代码&#xff0c;原因是因为对象的configurable属性为false 这样如果需要对document对象使用JS进行hook,首先需要一个可配置的chrome浏览器&#xff0c;可以在…

亚信科技基于 Apache SeaTunnel 的二次开发应用实践

亚信科技在Apache SeaTunnel的实践分享 自我介绍 各位同学好&#xff0c;很荣幸通过Apache SeaTunnel社区和大家进行分享交流。我是来自亚信科技的潘志宏&#xff0c;主要负责公司内部数据中台产品的开发。 本次分享的主题是Apache SeaTunnel在亚信科技的集成实践&#xff0c…