it编程 > 数据库 > PostgreSQL

PostgreSQL行级安全策略探究

165人参与 2024-08-04 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个用户

  
  
  
  1. postgres=# create user admin;

  2. create role

  3. postgres=# create user peter;

  4. create role

  5. postgres=# create user bob;

  6. create role

创建一个rlsdb数据库

  
  
  
  1. postgres=# create database rlsdb owner admin;

  2. create database

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

  
  
  
  1. postgres=# \c rlsdb admin

  2. you are now connected to database "rlsdb" as user "admin".

  3. rlsdb=> create table employee ( empno int, ename text, address text, salary int, account_number text );

  4. create table

  5. rlsdb=> insert into employee values (1, 'admin', '2 down str', 80000, 'no0001' );

  6. insert 0 1

  7. rlsdb=> insert into employee values (2, 'peter', '132 south avn', 60000, 'no0002' );

  8. insert 0 1

  9. rlsdb=> insert into employee values (3, 'bob', 'down st 17th', 60000, 'no0003' );

  10. insert 0 1

  11. rlsdb=>

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

  
  
  
  1. rlsdb=> grant select on table employee to peter;

  2. grant

  3. rlsdb=> grant select on table employee to bob;

  4. grant

  5. rlsdb=> select * from employee;

  6. empno | ename | address | salary | account_number

  7. -------+-------+---------------+--------+----------------

  8. 1 | admin | 2 down str | 80000 | no0001

  9. 2 | peter | 132 south avn | 60000 | no0002

  10. 3 | bob | down st 17th | 60000 | no0003

  11. (3 rows)

  12. rlsdb=>

  13. rlsdb=> \c rlsdb peter

  14. you are now connected to database "rlsdb" as user "peter".

  15. rlsdb=> select * from employee;

  16. empno | ename | address | salary | account_number

  17. -------+-------+---------------+--------+----------------

  18. 1 | admin | 2 down str | 80000 | no0001

  19. 2 | peter | 132 south avn | 60000 | no0002

  20. 3 | bob | down st 17th | 60000 | no0003

  21. (3 rows)

  22. rlsdb=> \c rlsdb bob

  23. you are now connected to database "rlsdb" as user "bob".

  24. rlsdb=> select * from employee;

  25. empno | ename | address | salary | account_number

  26. -------+-------+---------------+--------+----------------

  27. 1 | admin | 2 down str | 80000 | no0001

  28. 2 | peter | 132 south avn | 60000 | no0002

  29. 3 | bob | down st 17th | 60000 | no0003

  30. (3 rows)

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

  
  
  
  1. rlsdb=> \c rlsdb admin

  2. you are now connected to database "rlsdb" as user "admin".

  3. rlsdb=> create policy emp_rls_policy on employee for all to public using (ename=current_user);

  4. create policy

  5. rlsdb=> alter table employee enable row level security;

  6. alter table

  7. rlsdb=> \c rlsdb peter

  8. you are now connected to database "rlsdb" as user "peter".

  9. rlsdb=> select * from employee;

  10. empno | ename | address | salary | account_number

  11. -------+-------+---------------+--------+----------------

  12. 2 | peter | 132 south avn | 60000 | no0002

  13. (1 row)

  14. rlsdb=> \c rlsdb bob

  15. you are now connected to database "rlsdb" as user "bob".

  16. rlsdb=> select * from employee;

  17. empno | ename | address | salary | account_number

  18. -------+-------+--------------+--------+----------------

  19. 3 | bob | down st 17th | 60000 | no0003

  20. (1 row)

  21. rlsdb=>

行级安全原理

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

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

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

  
  
  
  1. objectaddress

  2. createpolicy(createpolicystmt *stmt)

  3. {

  4. /*省略部分代码行*/

  5. /*将策略转化为where子句*/

  6. qual = transformwhereclause(qual_pstate,

  7. stmt->qual,

  8. expr_kind_policy,

  9. "policy");

  10. with_check_qual = transformwhereclause(with_check_pstate,

  11. stmt->with_check,

  12. expr_kind_policy,

  13. "policy");

  14. /* fix up collation information */

  15. assign_expr_collations(qual_pstate, qual);

  16. assign_expr_collations(with_check_pstate, with_check_qual);

  17. /* 将转换后的子句写入pg_policy*/

  18. /* open pg_policy catalog */

  19. pg_policy_rel = table_open(policyrelationid, rowexclusivelock);

  20. /* set key - policy's relation id. */

  21. scankeyinit(&skey[0],

  22. anum_pg_policy_polrelid,

  23. btequalstrategynumber, f_oideq,

  24. objectidgetdatum(table_id));

  25. /* set key - policy's name. */

  26. scankeyinit(&skey[1],

  27. anum_pg_policy_polname,

  28. btequalstrategynumber, f_nameeq,

  29. cstringgetdatum(stmt->policy_name));

  30. sscan = systable_beginscan(pg_policy_rel,

  31. policypolrelidpolnameindexid, true, null, 2,

  32. skey);

  33. policy_tuple = systable_getnext(sscan);

  34. /* complain if the policy name already exists for the table */

  35. if (heaptupleisvalid(policy_tuple))

  36. ereport(error,

  37. (errcode(errcode_duplicate_object),

  38. errmsg("policy \"%s\" for table \"%s\" already exists",

  39. stmt->policy_name, relationgetrelationname(target_table))));

  40. policy_id = getnewoidwithindex(pg_policy_rel, policyoidindexid,

  41. anum_pg_policy_oid);

  42. values[anum_pg_policy_oid - 1] = objectidgetdatum(policy_id);

  43. values[anum_pg_policy_polrelid - 1] = objectidgetdatum(table_id);

  44. values[anum_pg_policy_polname - 1] = directfunctioncall1(namein,

  45. cstringgetdatum(stmt->policy_name));

  46. values[anum_pg_policy_polcmd - 1] = chargetdatum(polcmd);

  47. values[anum_pg_policy_polpermissive - 1] = boolgetdatum(stmt->permissive);

  48. values[anum_pg_policy_polroles - 1] = pointergetdatum(role_ids);

  49. /* add qual if present. */

  50. if (qual)

  51. values[anum_pg_policy_polqual - 1] = cstringgettextdatum(nodetostring(qual));

  52. else

  53. isnull[anum_pg_policy_polqual - 1] = true;

  54. /* add with check qual if present */

  55. if (with_check_qual)

  56. values[anum_pg_policy_polwithcheck - 1] = cstringgettextdatum(nodetostring(with_check_qual));

  57. else

  58. isnull[anum_pg_policy_polwithcheck - 1] = true;

  59. policy_tuple = heap_form_tuple(relationgetdescr(pg_policy_rel), values,

  60. isnull);

  61. catalogtupleinsert(pg_policy_rel, policy_tuple);

  62. /* record dependencies */

  63. target.classid = relationrelationid;

  64. target.objectid = table_id;

  65. target.objectsubid = 0;

  66. myself.classid = policyrelationid;

  67. myself.objectid = policy_id;

  68. myself.objectsubid = 0;

  69. recorddependencyon(&myself, &target, dependency_auto);

  70. recorddependencyonexpr(&myself, qual, qual_pstate->p_rtable,

  71. dependency_normal);

  72. recorddependencyonexpr(&myself, with_check_qual,

  73. with_check_pstate->p_rtable, dependency_normal);

  74. /* register role dependencies */

  75. target.classid = authidrelationid;

  76. target.objectsubid = 0;

  77. for (i = 0; i < nitems; i++)

  78. {

  79. target.objectid = datumgetobjectid(role_oids[i]);

  80. /* no dependency if public */

  81. if (target.objectid != acl_id_public)

  82. recordshareddependencyon(&myself, &target,

  83. shared_dependency_policy);

  84. }

  85. invokeobjectpostcreatehook(policyrelationid, policy_id, 0);

  86. /* invalidate relation cache */

  87. cacheinvalidaterelcache(target_table);

  88. /* clean up. */

  89. heap_freetuple(policy_tuple);

  90. free_parsestate(qual_pstate);

  91. free_parsestate(with_check_pstate);

  92. systable_endscan(sscan);

  93. relation_close(target_table, nolock);

  94. table_close(pg_policy_rel, rowexclusivelock);

  95. return myself;

  96. }

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

  
  
  
  1. rlsdb=> explain analyze select * from employee ;

  2. query plan

  3. -----------------------------------------------------------------------------------------------------

  4. seq scan on employee (cost=0.00..19.15 rows=3 width=104) (actual time=0.010..0.012 rows=1 loops=1)

  5. filter: (ename = current_user)

  6. rows removed by filter: 2

  7. planning time: 0.416 ms

  8. execution time: 0.036 ms

  9. (5 rows)

  10. 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,生成执行计划去执行。

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



(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

PostgreSQL ACE 深度访谈 | 第四期 唐成

08-04

中国PG分会&美创科技 携手开展PostgreSQL企业内训营

08-04

成员更新 | 2024上半年新晋PostgreSQL ACE

08-04

国际资讯:EDB从Postgres数据库公司转型为Postgres数据和AI平台公司

08-04

Stackoverflow年度报告:PostgreSQL蝉联“最受欢迎数据库” !

08-04

PG ACE 深度访谈 | 第三期 digoal(德哥)

08-04

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论