PostgreSQL行级安全策略探究

前言

最近和朋友讨论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,生成执行计划去执行。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

R包:‘ggcharts好看线图包‘

介绍 ggcharts提供了一个高级{ggplot2}接口&#xff0c;用于创建通用图表。它的目标既简单又雄心勃勃:让您更快地从数据可视化的想法到实际的绘图。所以如何?通过处理大量的数据预处理&#xff0c;为您模糊{ggplot2}细节和绘图样式。生成的图是ggplot对象&#xff0c;可以使用…

CTF php RCE(三)

0x07 日志文件包含 判断类型 使用kali curl -I urlF12 打开F12开发者工具&#xff0c;选中之后F5刷新查看server类型即可 配置文件 直接包含或者访问如果有回显就是&#xff0c; NGINX&#xff1a;NGINX 的配置文件通常位于 /etc/nginx/ 目录下&#xff0c;具体的网站配…

【深度学习入门篇 ④ 】Pytorch实现手写数字识别

【&#x1f34a;易编橙&#xff1a;一个帮助编程小伙伴少走弯路的终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…

LLMs可以进行任务规划吗?如果不行,LLMs+GNN可以吗?

深度图学习与大模型LLM(小编): 大家好,今天向大家介绍一篇最新发布的研究论文&#xff08;20240530&#xff09;。这篇论文探讨了如何通过引入GNN来提高大模型在任务规划(task planning)中的性能。*论文分析了LLMs在任务规划上的局限性,并提出了一种简单而有效的解决方案。* 1.…

VIM模式之间的切换

命令行界面下&#xff0c;常用的文本编辑器是 VI / VIM(VI增强版)&#xff0c;VI 是 Linux 最通用的文本编辑器&#xff0c;VIM相较于VI&#xff0c;提供了代码高亮等功能&#xff0c;两者用法完全兼容&#xff1b; 1. 进入 VIM 工作界面 vim 文件名 2. 进入编辑模式 三种方…

深入分析与解决4.3问题:iOS应用版本更新审核被拒原因解析

深入分析与解决4.3问题&#xff1a;iOS应用版本更新审核被拒原因解析 在iOS应用开发和发布过程中&#xff0c;遇到4.3问题&#xff08;设计 - 垃圾邮件&#xff09;是一个常见且令人头疼的情况。即使您的应用已成功发布其第一个版本&#xff0c;但在进行版本更新时&#xff0c…

【React Hooks原理 - useState】

概述 useState赋予了Function Component状态管理的能力&#xff0c;可以让你在不编写 class 的情况下使用 state 。其本质上就是一类特殊的函数&#xff0c;它们约定以 use 开头。本文从源码出发&#xff0c;一步一步看看useState是如何实现以及工作的。 基础使用 function …

数据结构day6链式队列

主程序 #include "fun.h" int main(int argc, const char *argv[]) { que_p Qcreate(); enqueue(Q,10); enqueue(Q,20); enqueue(Q,30); enqueue(Q,40); enqueue(Q,50); show_que(Q); dequeue(Q); show_que(Q); printf(&qu…

小程序复制功能不可用 setClipboardData:fail no permission

先上图 用户协议剪切板也更新但是依旧报错了 最后在公众平台通知里发现是用户之前小程序有规格被封禁了该功能

【常见开源库的二次开发】基于openssl的加密与解密——openssl认识与配置(一)

目录&#xff1a; 目录&#xff1a; 一、什么是openssl&#xff1f; 二、所需要具备的开发工具 三、Windows上编译OpenSSL3.0 四、Linux编译openssl3.0 一、什么是openssl&#xff1f; OpenSSL 是一个开源的软件库&#xff0c;它提供了一系列加密工具和协议&#xff0c;主要用…

apple watch程序出错 Cannot launch apps while in nightstand mode

开发的时候运行apple watch程序出错&#xff1a; ailure Reason: The request was denied by service delegate (IOSSHLMainWorkspace) for reason: Busy ("Cannot launch apps while in nightstand mode"). 这是因为&#xff1a; 将Apple Watch放在充电器上并直立…

Python 处理文件的读写操作

Python 提供了丰富的文件读写操作&#xff0c;可以轻松处理文本文件和二进制文件。以下是关于 Python 文件读写操作的详细讲解&#xff0c;包括打开文件、读取文件、写入文件、文件指针操作、文件关闭和异常处理等方面。 一、文件的打开和关闭 在对文件进行读写操作之前&…

喜讯|华院计算法律大模型入围《2024大模型典型示范应用案例集》

2024年世界人工智能大会&#xff08;WAIC&#xff09;举办期间&#xff0c;中国信通院正式发布了《2024大模型典型示范应用案例集》&#xff08;以下简称《案例集》&#xff09;。该案例集由中国信通院华东分院、上海人工智能实验室主导&#xff0c;以产业化为导向&#xff0c;…

探索IP形象设计:快速掌握设计要点

随着市场竞争的加剧&#xff0c;越来越多的企业开始关注品牌形象的塑造和推广。在品牌形象中&#xff0c;知识产权形象设计是非常重要的方面。在智能和互联网的趋势下&#xff0c;未来的知识产权形象设计可能会更加关注数字和社交网络。通过数字技术和社交媒体平台&#xff0c;…

Java 中的泛型(超全详解)

一、泛型概述 1. 什么是泛型&#xff1f;为什么要使用泛型&#xff1f; 泛型&#xff0c;即“参数化类型”。一提到参数&#xff0c;最熟悉的就是定义方法时有形参列表&#xff0c;普通方法的形参列表中&#xff0c;每个形参的数据类型是确定的&#xff0c;而变量是一个参数。在…

VS2022 git拉取/推送代码错误

第一步&#xff1a;打开VS2022 第二步&#xff1a;工具->选项->源代码管理->Git 全局设置 第三步&#xff1a;加密网络提供程序设置为&#xff1a;OpenSSL 完结&#xff1a;

函数式接口、匿名内部类、lambda表达式

一、函数式接口 只有一个抽象方法的接口叫函数式接口&#xff0c;不能有两个&#xff0c;也不能有方法实现。 FunctionalInterface注解标记&#xff0c;在idea中可以用这个注解验证是不是函数式接口。实现函数式接口可以转成lambda表达式。 二、匿名内部类 匿名内部类的格式&a…

Java面试八股之Redis单线程为什么性能高

Redis单线程为什么性能高 1.内存数据库特性 要点&#xff1a;Redis是一个内存数据库&#xff0c;其数据主要存储在内存中&#xff0c;而非磁盘。内存访问的速度远超磁盘&#xff0c;通常可达纳秒级别&#xff0c;这使得Redis在处理数据时几乎不受I/O瓶颈的影响。由于数据操作…

Python31 自然语言处理NLP之NLTK的使用

1.关于自然语言处理NLP 自然语言处理NLP是人工智能和计算机科学的一个子领域&#xff0c;专注于计算机与人类&#xff08;自然&#xff09;语言之间的互动。其目的是使计算机能够理解、解释和生成人类语言。NLP 涉及语言学、计算机科学和人工智能的多学科交叉&#xff0c;通过…

SAP与税控系统集成案例

一、项目背景 重庆润通控股有限公司成立于2007年&#xff0c;是一家集合汽柴油动力及终端、摩托车、储能电源、汽车零部件、金融服务等产业的多元化集团公司。 大量订单数据导致订单业务会很复杂&#xff0c;为提供订单完成质量&#xff0c;引入税控系统服务商进行订单开票…