规范化表上 Postgres 中的行级安全性
Row Level Security in Postgres on Normalized Tables
前提
In documentation,行级安全性似乎很棒。根据我读过的内容,我现在可以停止创建这样的视图:
SELECT data.*
FROM data
JOIN user_data
ON data.id = user_data.data_id
AND user_data.role = CURRENT_ROLE
最重要的是,Postgres 对此进行了很好的分析 view
从索引扫描开始,然后在 user_data
table 上进行散列连接,这正是我们想要发生的事情因为它太快了。将其与我的 RLS 实现进行比较:
CREATE POLICY data_owner
ON data
FOR ALL
TO user
USING (
(
SELECT TRUE AS BOOL FROM (
SELECT data_id FROM user_data WHERE user_role = CURRENT_USER
) AS user_data WHERE user_data.data_id = data.id
) = true
)
WITH CHECK (TRUE);
这个糟糕的策略执行 data
table 中的 每一行 的条件,而不是通过将查询范围限定到我们的 CURRENT_USER
可以访问,就像我们的视图一样。需要明确的是,这意味着 select * from data
命中 data
table.
中的 每个 行
问题
我如何编写一个带有内部 select
的策略,它不会在目标 table 的 每个 行上测试 said select
].换句话说:如何让 RLS 运行 我对目标 table 的策略在 运行 对结果进行实际查询之前?
p.s。我让这个问题变得含糊不清并且 fiddle-less,主要是因为 sqlfiddle 还没有达到 9.5。如果我需要添加更多颜色或一些要点来解决我的问题,请告诉我。
如果您这样表述策略,PostgreSQL 可能会生成更好的计划:
...
USING (EXISTS
(SELECT data_id
FROM user_data
WHERE user_data.data_id = data.id
AND role = current_user
)
)
你应该有一个 (PRIMARY KEY
?) 索引 ON user_data (role, data_id)
来加速嵌套循环连接。
但我认为将权限信息包含在 data
table 本身中可能是一个更好的设计,也许使用 name[]
类型:
CREATE TABLE data(
id integer PRIMARY KEY,
val text,
acl name[] NOT NULL
);
INSERT INTO data VALUES (1, 'one', ARRAY[name 'laurenz', name 'advpg']);
INSERT INTO data VALUES (2, 'two', ARRAY[name 'advpg']);
INSERT INTO data VALUES (3, 'three', ARRAY[name 'laurenz']);
那么你可以使用这样的策略:
CREATE POLICY data_owner ON data FOR ALL TO PUBLIC
USING (acl @> ARRAY[current_user::name])
WITH CHECK (TRUE);
ALTER TABLE data ENABLE ROW LEVEL SECURITY;
ALTER TABLE data FORCE ROW LEVEL SECURITY;
当我 SELECT
时,我只得到我有权限的行:
SELECT id, val FROM data;
id | val
----+-------
1 | one
3 | three
(2 rows)
您可以定义一个 GIN 索引来支持该条件:
CREATE INDEX ON data USING gin (acl _name_ops);
前提
In documentation,行级安全性似乎很棒。根据我读过的内容,我现在可以停止创建这样的视图:
SELECT data.*
FROM data
JOIN user_data
ON data.id = user_data.data_id
AND user_data.role = CURRENT_ROLE
最重要的是,Postgres 对此进行了很好的分析 view
从索引扫描开始,然后在 user_data
table 上进行散列连接,这正是我们想要发生的事情因为它太快了。将其与我的 RLS 实现进行比较:
CREATE POLICY data_owner
ON data
FOR ALL
TO user
USING (
(
SELECT TRUE AS BOOL FROM (
SELECT data_id FROM user_data WHERE user_role = CURRENT_USER
) AS user_data WHERE user_data.data_id = data.id
) = true
)
WITH CHECK (TRUE);
这个糟糕的策略执行 data
table 中的 每一行 的条件,而不是通过将查询范围限定到我们的 CURRENT_USER
可以访问,就像我们的视图一样。需要明确的是,这意味着 select * from data
命中 data
table.
问题
我如何编写一个带有内部 select
的策略,它不会在目标 table 的 每个 行上测试 said select
].换句话说:如何让 RLS 运行 我对目标 table 的策略在 运行 对结果进行实际查询之前?
p.s。我让这个问题变得含糊不清并且 fiddle-less,主要是因为 sqlfiddle 还没有达到 9.5。如果我需要添加更多颜色或一些要点来解决我的问题,请告诉我。
如果您这样表述策略,PostgreSQL 可能会生成更好的计划:
...
USING (EXISTS
(SELECT data_id
FROM user_data
WHERE user_data.data_id = data.id
AND role = current_user
)
)
你应该有一个 (PRIMARY KEY
?) 索引 ON user_data (role, data_id)
来加速嵌套循环连接。
但我认为将权限信息包含在 data
table 本身中可能是一个更好的设计,也许使用 name[]
类型:
CREATE TABLE data(
id integer PRIMARY KEY,
val text,
acl name[] NOT NULL
);
INSERT INTO data VALUES (1, 'one', ARRAY[name 'laurenz', name 'advpg']);
INSERT INTO data VALUES (2, 'two', ARRAY[name 'advpg']);
INSERT INTO data VALUES (3, 'three', ARRAY[name 'laurenz']);
那么你可以使用这样的策略:
CREATE POLICY data_owner ON data FOR ALL TO PUBLIC
USING (acl @> ARRAY[current_user::name])
WITH CHECK (TRUE);
ALTER TABLE data ENABLE ROW LEVEL SECURITY;
ALTER TABLE data FORCE ROW LEVEL SECURITY;
当我 SELECT
时,我只得到我有权限的行:
SELECT id, val FROM data;
id | val
----+-------
1 | one
3 | three
(2 rows)
您可以定义一个 GIN 索引来支持该条件:
CREATE INDEX ON data USING gin (acl _name_ops);