【发布时间】:2021-10-03 02:49:43
【问题描述】:
背景
这些天我在工作中组织一个项目的 Postgres 数据库。
编辑: 该数据库连接到在其上运行 Postgraphile 的 NodeJS 服务器,以便为客户端公开 GraphQL 接口。因此,我必须使用RLS来禁止用户查询和操作他/她没有权限的行。
我的任务之一是为每个表添加一个deleted 字段,并使用RLS 隐藏deleted = true 的记录。
代码示例
为了解释我的问题,我将添加一个用于构建假数据库的 SQL 代码:
角色
在本例中,我将使用以下角色:
- 名为
admin的超级用户角色 - 角色名为
app_users - 2 用户继承自
app_users:bobalice
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 *;
编辑:现在,重要的是要了解,上面定义的策略在我进行任何查询时都有效,只要:
- INSERT INTO 不包括
deleted = true(没关系)。 - 包含
SET deleted = true的更新。 (这是主要问题)。
我想:
- 允许 bob 在 UPDATE 命令上使用
deleted = true删除记录。 - 在 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