【问题标题】:Postgres Row Level Security doesn't allow update on restrictive select policyPostgres 行级安全性不允许更新限制性选择策略
【发布时间】:2021-10-03 02:49:43
【问题描述】:

背景

这些天我在工作中组织一个项目的 Postgres 数据库。

编辑: 该数据库连接到在其上运行 Postgraphile 的 NodeJS 服务器,以便为客户端公开 GraphQL 接口。因此,我必须使用RLS来禁止用户查询和操作他/她没有权限的行。

我的任务之一是为每个表添加一个deleted 字段,并使用RLS 隐藏deleted = true 的记录。

代码示例

为了解释我的问题,我将添加一个用于构建假数据库的 SQL 代码:

角色

在本例中,我将使用以下角色:

  • 名为admin 的超级用户角色
  • 角色名为app_users
  • 2 用户继承自app_users
    • bob
    • alice
CREATE ROLE admin WITH
  LOGIN
  SUPERUSER
  INHERIT
  CREATEDB
  CREATEROLE
  NOREPLICATION
  ENCRYPTED PASSWORD 'md5f6fdffe48c908deb0f4c3bd36c032e72'; -- password: admin

GRANT username TO admin;

CREATE ROLE app_users WITH
  NOLOGIN
  NOSUPERUSER
  NOINHERIT
  NOCREATEDB
  CREATEROLE
  NOREPLICATION;

CREATE ROLE bob WITH
  LOGIN
  NOSUPERUSER
  INHERIT
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION
  ENCRYPTED PASSWORD 'md5e8557d12f6551b2ddd26bbdd0395465c';

GRANT app_users TO bob;

CREATE ROLE alice WITH
  LOGIN
  NOSUPERUSER
  INHERIT
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION
  ENCRYPTED PASSWORD 'md5579e43b423b454623383471aeb85cd87';

GRANT app_users TO alice;

数据库

此示例将保存一个名为 league 的数据库,用于美式橄榄球联盟的模拟数据库。

CREATE DATABASE league
    WITH 
    OWNER = admin
    ENCODING = 'UTF8'
    LC_COLLATE = 'en_US.utf8'
    LC_CTYPE = 'en_US.utf8'
    TABLESPACE = pg_default
    CONNECTION LIMIT = -1;

GRANT CREATE, CONNECT ON DATABASE league TO admin;
GRANT TEMPORARY ON DATABASE league TO admin WITH GRANT OPTION;

GRANT TEMPORARY, CONNECT ON DATABASE league TO PUBLIC;

方案:public

我在方案中添加了一些小改动,所以默认情况下,角色app_users 允许任何命令、类型、执行函数等。

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO app_users;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO app_users;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT EXECUTE ON FUNCTIONS TO app_users;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE ON TYPES TO app_users;

创建表格

表:团队

CREATE TABLE IF NOT EXISTS public."TEAMS"
(
    id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
    deleted boolean NOT NULL DEFAULT false,
    name text COLLATE pg_catalog."default" NOT NULL,
    owner text COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT "TEAMS_pkey" PRIMARY KEY (id)
)

TABLESPACE pg_default;

ALTER TABLE public."TEAMS"
    OWNER to admin;

ALTER TABLE public."TEAMS"
    ENABLE ROW LEVEL SECURITY;

GRANT ALL ON TABLE public."TEAMS" TO admin;
GRANT ALL ON TABLE public."TEAMS" TO app_users;

CREATE POLICY teams_deleted
    ON public."TEAMS"
    AS RESTRICTIVE
    FOR SELECT
    TO app_users
    USING (deleted = false);

CREATE POLICY teams_owner
    ON public."TEAMS"
    AS PERMISSIVE
    FOR ALL
    TO app_users
    USING (owner = CURRENT_USER);

表:球员

CREATE TABLE IF NOT EXISTS public."PLAYERS"
(
    id text COLLATE pg_catalog."default" NOT NULL,
    deleted boolean NOT NULL DEFAULT false,
    first_name text COLLATE pg_catalog."default" NOT NULL,
    last_name text COLLATE pg_catalog."default" NOT NULL,
    team_id integer NOT NULL,
    jersey_number integer NOT NULL,
    CONSTRAINT "PLAYERS_pkey" PRIMARY KEY (id),
    CONSTRAINT fkey_team_id FOREIGN KEY (team_id)
        REFERENCES public."TEAMS" (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE CASCADE,
    CONSTRAINT check_player_number CHECK (jersey_number > 0 AND jersey_number < 100)
)

TABLESPACE pg_default;

ALTER TABLE public."PLAYERS"
    OWNER to admin;

ALTER TABLE public."PLAYERS"
    ENABLE ROW LEVEL SECURITY;

GRANT ALL ON TABLE public."PLAYERS" TO admin;
GRANT ALL ON TABLE public."PLAYERS" TO app_users;

CREATE POLICY players_deleted
    ON public."PLAYERS"
    AS RESTRICTIVE
    FOR SELECT
    TO app_users
    USING (deleted = false);

CREATE POLICY players_owner
    ON public."PLAYERS"
    AS PERMISSIVE
    FOR ALL
    TO app_users
    USING ((( SELECT "TEAMS".owner
   FROM "TEAMS"
  WHERE ("TEAMS".id = "PLAYERS".team_id)) = CURRENT_USER));

测试用例(为更好理解而编辑)

使用用户bob运行此代码:

INSERT INTO "TEAMS" (name, owner)
    VALUES ('Jerusalem Lions', 'bob')
    RETURNING id; -- We'll save this id for the next command

INSERT INTO "PLAYERS" (id, first_name, last_name, jersey_number, role, team_id)
    VALUES ('99999', 'Eric', 'Cohen', 29, 'linebacker', 888) -- Replace 888 with the returned id from the previous command
    RETURNING *;

-- These commands will work
SELECT * FROM "PLAYERS";

UPDATE "PLAYERS"
    SET last_name = 'Levi'
    WHERE id = '99999'
    RETURNING *; 

-- This is the command that won't work. I can't change the deleted. 
UPDATE "PLAYERS"
    SET deleted = true
    WHERE id = '99999'
    RETURNING *;

编辑:现在,重要的是要了解,上面定义的策略在我进行任何查询时都有效,只要:

  1. INSERT INTO 不包括 deleted = true(没关系)。
  2. 包含SET deleted = true 的更新。 (这是主要问题)。

我想:

  1. 允许 bob 在 UPDATE 命令上使用 deleted = true 删除记录。
  2. 在 SELECT 命令中隐藏deleted = true 的所有记录。

我该怎么办? ??????♂️

【问题讨论】:

  • RLS 似乎有点矫枉过正。为什么不通过一个没有指定 WITH CHECK OPTION 的简单 VIEW 提供访问权限?
  • 您的策略players_owner 将导致错误,因为USING 表达式不会返回boolean
  • 1.我之所以使用RLS是因为这个DB会通过Postgraphile暴露出来,所以我不想让用户获取表的所有数据。
  • 2.条件正常,并且对于不会将 deleted 更改为 true 的任何命令都可以正常工作。
  • “最后一个命令将失败”是因为什么?哪个 RLS 定义被冒犯了?

标签: sql postgresql row-level-security


【解决方案1】:

来自the documentation:

当同时存在许可政策和限制政策时,除了所有限制政策之外,只有在至少一项许可政策通过时,才能访问记录。

这意味着您不能在一项政策中允许另一项政策中限制的内容。因此,应极其谨慎地使用限制性策略。当没有定义政策时,一切都受到限制,因此您应该专注于允许应该允许的事情。

简化示例:

create table my_table(
    id int primary key, 
    user_name text, 
    deleted bool);
    
alter table my_table enable row level security;

create policy rule_for_select
    on my_table
    as permissive
    for select
    to app_users
    using (not deleted);

create policy rule_for_all
    on my_table
    as permissive
    for all
    to app_users
    using (user_name = current_user and not deleted);

insert into my_table(id, user_name, deleted) values
(1, 'alice', false),
(2, 'bob', true),
(3, 'celine', true)

用户bob 将看到第1 行。如果deleted 为假,他将能够更新第2 行。

【讨论】:

  • 这是问题所在:Permissive 的工作方式类似于 OR 条件,我不只是 bob 作为用户,我有 @987654326 @ 也是如此,他们从名为app_users 的角色继承了他们的权限。当我写下这些政策时,我必须写TO app_users 而不是TO bob
  • 没有问题,因为我用过user_name = current_user,这个例子也适用于app_users(我已经分别编辑了答案)。你没有抓住主要问题。您可以(并且应该)仅使用许可策略定义您需要的所有内容,因为您无法覆盖限制性策略。
  • 我已经尝试过这两种许可策略的组合。 Bob 正在使用deleted = true 获取记录。没有达到我正在寻找的东西。我不想使用deleted = true 获取记录,但我确实希望允许用户 bob(或app_users 中的任何用户)更新字段deleted 的值。
  • 好的,只需像更新的答案一样将条件添加到策略中。
  • 也不行
【解决方案2】:

我能收集到的是,那个

UPDATE "PLAYERS" SET deleted=true

不会因为“Bob”而失败。但是:

UPDATE "PLAYERS" SET deleted=true RETURNING *

会的。 Postgraphile 在进行突变时通常会“返回”一些东西。更新行后 - 你基本上禁止选择语句。

Postgraphile 领域的常用技巧(功能)通常是创建一个custom mutation 并将其定义为security definer

也许还有其他方法可以解决这个问题 - 但根据我的经验,这是 Postgraphile 中的锤子->钉子方法。

【讨论】:

  • 1.突变后的SELECT,由于 Postgrapile 的工作原理,这是一个很好的观点,我会注意到这一点。 2.当我在Postgres Shell中做UPDATE public."PLAYERS" SET deleted = true WHERE id = '11111';时,它失败了,所以这个bug不仅仅与Postgraphile有关,它也与SQL本身有关。
猜你喜欢
  • 2016-03-14
  • 1970-01-01
  • 2019-03-05
  • 2021-12-18
  • 2016-06-28
  • 1970-01-01
  • 1970-01-01
  • 2022-07-07
  • 2017-06-16
相关资源
最近更新 更多