Car*_*rre 5 postgresql triggers row-level-security
语境
我将行级安全性与触发器一起使用,以实现纯SQL RBAC实现。这样做时,我在INSERT触发器和SELECT行级安全策略之间遇到了奇怪的行为。
为简单起见,本问题的其余部分将使用以下简化表讨论该问题:
CREATE TABLE a (id TEXT);
ALTER TABLE a ENABLE ROW LEVEL SECURITY;
ALTER TABLE a FORCE ROW LEVEL SECURITY;
CREATE TABLE b (id TEXT);
Run Code Online (Sandbox Code Playgroud)
问题
考虑以下策略和触发器:
CREATE POLICY aSelect ON a FOR SELECT
USING (EXISTS(
select * from b where a.id = b.id
));
CREATE POLICY aInsert ON a FOR INSERT
WITH CHECK (true);
CREATE FUNCTION reproHandler() RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE USING MESSAGE = 'inside trigger handler';
INSERT INTO b (id) VALUES (NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER reproTrigger BEFORE INSERT ON a
FOR EACH ROW EXECUTE PROCEDURE reproHandler();
Run Code Online (Sandbox Code Playgroud)
现在考虑以下语句:
INSERT INTO a VALUES ('fails') returning id;
Run Code Online (Sandbox Code Playgroud)
基于阅读命令类型表所应用的策略以及对SQL的一般理解,我的期望是应该按顺序执行以下操作:
('fails')上演新行INSERTBEFORE触发器触发与NEW设置为新行('fails')插入到b触发器过程中并从中返回,并保持不变INSERT的WITH CHECK政策true进行评估,以trueSELECT的USING政策select * from b where a.id = b.id。 由于步骤3,它应该返回true('fails')插入表中fails返回插入行的ID()不幸的是(您可能已经猜到了),而不是发生上述步骤,我们看到了:
test=> INSERT INTO a VALUES ('fails') returning id;
NOTICE: inside trigger handler
ERROR: new row violates row-level security policy for table "a"
Run Code Online (Sandbox Code Playgroud)
这个问题的目的是发现为什么没有发生预期的行为。
请注意以下语句按预期正确运行:
test=> INSERT INTO a VALUES ('works');
NOTICE: inside trigger handler
INSERT 0 1
test=> select * from a; select * from b;
id
-------
works
(1 row)
id
-------
works
(1 row)
Run Code Online (Sandbox Code Playgroud)
我尝试了什么?
BEFOREvs的实验AFTERAFTER导致触发不执行在所有ALL命令的单个策略(使用相同的使用/带有检查表达式)
附录
PostgreSQL 10.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit在与通用邮件列表中的其他 PostgreSQL 用户/开发人员反复交流后,确定此特定问题是由单个语句中的突变可见性引起的。 您可以在此处查看整个讨论。特别感谢 Dean Rasheed 解释了问题并提出了解决方案。为了 Stack Overflow 社区的利益,我在这里总结了他的回答。
综上所述,触发器插入EXISTS的行在行级安全性中的后续子句是不可见的SELECT由于整个语句在单个 PostgreSQL 快照中运行策略中。
解决此问题的一种方法是确保EXISTS使用新快照运行该子句。为此,该EXISTS子句可以使用标记为 的 PostgreSQL 函数VOLATILE。此函数属性将使函数能够观察在同一语句中所做的更改。有关更多信息,请参阅文档。此处摘录相关段落以供参考:
对于用 SQL 或任何标准过程语言编写的函数,还有一个由易变性类别决定的第二个重要属性,即调用该函数的 SQL 命令所做的任何数据更改的可见性。VOLATILE 函数会看到这样的变化,而 STABLE 或 IMMUTABLE 函数不会。此行为是使用 MVCC 的快照行为实现的(请参阅第 13 章):STABLE 和 IMMUTABLE 函数使用在调用查询开始时建立的快照,而 VOLATILE 函数在它们执行的每个查询开始时获取新快照。
因此,此问题的一种解决方案是将 RLS 选择策略实现为VOLATILE函数。对政策的修改示例如下:
CREATE OR REPLACE FUNCTION rlsCheck(_id text) RETURNS TABLE (id text) AS $$
select * from b where b.id = _id
$$ LANGUAGE sql VOLATILE;
CREATE POLICY reproPolicySelect ON a FOR SELECT
USING (
EXISTS(select * from rlsCheck(a.id))
);
Run Code Online (Sandbox Code Playgroud)
在此解决方案中,从 table 投影的每一行都a要求该函数rlsCheck至少返回一行。此函数将使用每个投影行的新快照运行。每次调用生成的新快照rlsCheck将允许它看到表 b 的修改INSERT原始示例中触发器。
如果您进行上述修改并运行测试,您将看到以下行为:
test=> select * from a;
id
----
(0 rows)
test=> select * from b;
id
----
(0 rows)
test=> insert into a values ('hi') returning id;
NOTICE: inside trigger handler
id
----
hi
(1 row)
INSERT 0 1
Run Code Online (Sandbox Code Playgroud)
这种行为符合我的期望,所以我接受这是对问题的回答。不幸的是,该函数在查询执行期间会导致不可接受的优化栅栏,因此我不会在我的 RBAC 实现中使用它。我认为不可能有一个可优化的解决方案来解决我的问题,因为策略中的EXISTS表达式SELECT不能同时内联和 VOLATILE。
| 归档时间: |
|
| 查看次数: |
252 次 |
| 最近记录: |