【发布时间】:2021-10-01 17:08:00
【问题描述】:
我有启用 RLS 的基于 postgres (13.2) 的 API(我使用 postgraphile),它非常慢。 用户从 Google OAuth 发送 JWT。对表的访问基于角色(有 2 个:人员、管理员)+ RLS。 我有 2 个用户身份验证表:person、person_groups
CREATE TABLE IF NOT EXISTS myschema.person_groups (
id serial PRIMARY KEY,
person_id citext NOT NULL REFERENCES myschema.person (id),
google_id text NOT NULL REFERENCES myschema_private.person_account (google_id),
group_id serial NOT NULL REFERENCES myschema.groups (id),
updated_at timestamp DEFAULT now(),
CONSTRAINT unq_person_id_group_id UNIQUE (person_id, group_id)
);
CREATE INDEX persongroups_google_group_idx ON myschema.person_groups (google_id, group_id);
为了让 RLS 检查我将函数指定为:
CREATE OR REPLACE FUNCTION myschema.is_in_group (group_id int[])
RETURNS boolean
AS $$
SELECT
CASE WHEN current_setting('role', FALSE) = 'admin' THEN
TRUE
WHEN EXISTS (
SELECT
1
FROM
myschema.person_groups
WHERE
person_groups.group_id = ANY ($1) AND person_groups.google_id = current_setting('user.sub', TRUE)) THEN
TRUE
ELSE
FALSE
END
$$
LANGUAGE SQL
STABLE
STRICT
SECURITY DEFINER;
我有用户想要访问的表:“gate_enterlogs”。 此表的 RLS 为:
CREATE POLICY select_gate_enterlog ON myschema.gate_enterlog
FOR SELECT TO person
USING (myschema.is_in_group (ARRAY[6, 1]));
如果我使用这样的代码:
BEGIN;
SET local ROLE person;
SET local "user.sub" TO 'yyy';
EXPLAIN ANALYZE VERBOSE
SELECT COUNT(id) FROM myschema.gate_enterlog;
COMMIT;
我最终得到:
Aggregate (cost=23369.00..23369.01 rows=1 width=8) (actual time=2897.487..2897.487 rows=1 loops=1)
Output: count(id)
-> Seq Scan on myschema.gate_enterlog (cost=0.00..23297.08 rows=28769 width=4) (actual time=2897.484..2897.484 rows=0 loops=1)
Output: id, person_id, checkpoint_time, direction, place
Filter: is_in_group('{6,1}'::integer[])
Rows Removed by Filter: 86308
Planning Time: 0.626 ms
Execution Time: 2897.567 ms
如果我禁用 RLS 策略:
CREATE POLICY select_gate_enterlog ON myschema.gate_enterlog FOR SELECT TO person USING (TRUE);
Aggregate (cost=1935.85..1935.86 rows=1 width=8) (actual time=17.671..17.672 rows=1 loops=1)
Output: count(id)
-> Seq Scan on myschema.gate_enterlog (cost=0.00..1720.08 rows=86308 width=4) (actual time=0.008..7.364 rows=86308 loops=1)
Output: id, person_id, checkpoint_time, direction, place
Planning Time: 0.594 ms
Execution Time: 17.737 ms
您有什么想法可以优化 RLS,以便 postgres 会“记住”用户有权访问表。 我唯一的想法是在调用查询之前使用 USING (TRUE) 进行选择并授予访问权限,但在这样做之前,我希望有人能给我提示我做错了什么
【问题讨论】:
-
嗯,很奇怪,postgres 没有意识到
is_in_group('{6,1}'::integer[])是一个常量表达式,计算结果为FALSE,然后跳过扫描表…… -
我不知道您使用哪种身份验证机制,但是对于全表权限(策略不依赖于各个角色的值),普通用户/组权限似乎更多比RLS更适合。您可以根据用户是属于第 1 组还是第 6 组来切换数据库角色?然后只授予该角色对表的访问权限 (
GRANT SELECT …),并完全拒绝普通用户。 -
我正在使用这种方法来保证安全性:graphile.org/postgraphile/security。基本上用户获取 Google JWT 令牌,将其发送到 NodeJS 应用程序,应用程序检查 JWT 有效性,如果有效,则将 user_id 发送到 postgres 假设检查
is_in_group使用这种方法,管理员用户可以添加组,可以通过 api 将用户添加到组。据我所知,使用角色方法,管理员无法通过 api 创建“角色”(因为它不是普通表)。无论如何,我很好奇我做错了什么,在第一次运行is_in_group('{6,1}'::integer[])值之后,它没有针对其他行缓存。 -
我的意思是 postgraphile 不仅将
user_id发送到数据库it also gets therolefrom the JWT and uses that for the session。我不知道您如何使用 Google 进行配置,但是需要通过发布令牌的(Google)API 将用户添加到组中。我看不出在这里添加新组有什么关系,因为在您的情况下,成员资格检查无论如何都是硬编码的。但是,是的,如果您还需要动态组,这种方法会变得有点复杂 -
我有更动态的方法来检查 user_sub 是否在管理员应用的组中,但是当我试图缩小性能问题时,我创建了硬编码的组权限并且我没有想法。
标签: postgresql row-level-security