上下文
我正在使用行级安全性和触发器来实现纯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);
问题
请考虑以下策略和触发器:
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();
现在考虑以下语句:
INSERT INTO a VALUES ('fails') returning id;
基于对policies applied by command type table的理解和对SQL的一般理解,我的期望是以下事情应按顺序进行:
('fails')
暂存了新行INSERT
BEFORE
设置为新行的情况下触发NEW
触发器('fails')
插入到b
中,并从触发过程中直接返回INSERT
的{{1}}策略WITH CHECK
评估为true
true
的{{1}}策略SELECT
。 由于步骤3,它应该返回true USING
select * from b where a.id = b.id
)不幸的是(您可能已经猜到了),而不是上面发生的步骤,我们看到了:
('fails')
此问题的目的是发现为什么未发生预期的行为。
请注意,以下语句可以按预期正确运行:
fails
我尝试了什么?
test=> INSERT INTO a VALUES ('fails') returning id;
NOTICE: inside trigger handler
ERROR: new row violates row-level security policy for table "a"
和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)
进行了实验
BEFORE
导致触发器根本不执行 AFTER
命令的单个策略进行了实验(具有相同的using / with check表达式)
附录
AFTER
答案 0 :(得分:0)
与一般邮件列表上的其他PostgreSQL用户/开发人员来回交流后,确定此特定问题是由单个语句中的可见性引起的。 You can review the entire discussion here。特别感谢Dean Rasheed解释了问题并提出了解决方案。我在这里总结了他的答案,以使Stack Overflow社区受益。
总而言之,由于整个语句在单个PostgreSQL快照中运行,因此行级安全性EXISTS
策略中的后续SELECT
子句看不到触发器插入的行。
解决此问题的一种方法是确保EXISTS
子句与新快照一起运行。为此,EXISTS
子句可以使用标记为VOLATILE
的PostgreSQL函数。该函数属性将使函数能够观察同一语句内所做的更改。有关更多信息,请参见the documentation。相关段落摘录在这里供参考:
对于用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))
);
在此解决方案中,从表a
投影的每一行都将要求函数rlsCheck
返回至少一行。该功能将为每个计划的行运行一个新的快照。在原始示例中,每次调用rlsCheck
生成的新快照将使它能够查看由INSERT
触发器对表b的修改。
如果进行了上述修改并运行测试,您将看到以下行为:
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
此行为符合我的预期,因此我接受此作为问题的答案。不幸的是,该函数在查询执行期间导致了无法接受的优化范围,因此我将不在我的RBAC实现中使用它。我不认为有可能对我的问题有一个可优化的解决方案,因为EXISTS
策略中的SELECT
表达式不能同时内联和挥发。