PostfreSQL-行安全策略
除了通过 GRANT 提供的 SQL 标准权限系统之外,表还可以具有行安全策略,这些策略基于每个用户来限制正常查询可以返回哪些行,或者可以通过数据修改命令插入、更新或删除哪些行。此功能也称为行级安全性。默认情况下,表没有任何策略,因此如果用户根据 SQL 权限系统对表具有访问权限,则表中的所有行都可以同等地用于查询或更新。
当对表启用行安全性时(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY),行安全策略必须允许对表的所有正常访问以选择行或修改行。(但是,表的所有者通常不受行安全策略的约束。)如果表不存在策略,则使用默认拒绝策略,这意味着没有行可见或可以修改。适用于整个表的操作,例如 TRUNCATE 和 REFERENCES,不受行安全性的约束。
行安全策略可以特定于命令、角色或两者。可以指定策略以应用于所有命令,或应用于 SELECT、INSERT、UPDATE 或 DELETE。可以将多个角色分配给给定策略,并且应用正常的角色成员资格和继承规则。
要根据策略指定哪些行可见或可修改,需要一个返回布尔结果的表达式。在来自用户查询的任何条件或函数之前,将为每一行评估此表达式。(此规则的唯一例外是leakproof函数,它们保证不会泄漏信息;优化器可以选择在行安全检查之前应用此类函数。)表达式不返回 true 的行将不被处理。可以指定单独的表达式以提供对可见行和允许修改的行的独立控制。策略表达式作为查询的一部分运行,并以运行查询的用户的权限运行,尽管安全定义函数可用于访问调用用户不可用的数据。
具有 BYPASSRLS 属性的超级用户和角色在访问表时总是绕过行安全系统。表所有者通常也会绕过行安全性,尽管表所有者可以选择使用 ALTER TABLE ... FORCE ROW LEVEL SECURITY 来接受行安全性。
启用和禁用行安全性以及向表添加策略始终仅是表所有者的权限。
使用 CREATE POLICY 命令创建策略,使用 ALTER POLICY 命令更改策略,使用 DROP POLICY 命令删除策略。要启用和禁用给定表的行安全性,请使用 ALTER TABLE 命令。
每个策略都有一个名称,并且可以为一个表定义多个策略。由于策略是特定于表的,因此表的每个策略都必须具有唯一的名称。不同的表可能有同名的策略。
当多个策略应用于给定查询时,它们使用 OR(对于默认的许可策略)或 AND(对于限制性策略)进行组合。这类似于给定角色拥有其所属的所有角色的权限的规则。
举个简单的例子,这里是如何在账户关系上创建一个策略,只允许managers角色的成员访问行,并且只允许account表账户的行:
CREATE TABLE accounts (manager text, company text, contact_email text);
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY account_managers ON accounts TO managers
USING (manager = current_user);
如果未指定角色,或者使用了特殊用户名 PUBLIC,则该策略适用于系统上的所有用户。要允许所有用户仅访问users表中他们自己的行,可以使用一个简单的策略:
CREATE POLICY user_policy ON users
USING (user_name = current_user);
要对添加到表中的行与那些可见的行使用不同的策略,可以组合多个策略。这对策略将允许所有用户查看 users 表中的所有行,但只能修改自己的:
CREATE POLICY user_sel_policy ON users
FOR SELECT
USING (true);
CREATE POLICY user_mod_policy ON users
USING (user_name = current_user);
在 SELECT 命令中,这两个策略使用 OR 组合在一起,最终结果是可以选择所有行。在其他命令类型中,仅适用第二条策略,因此效果与之前相同。
也可以使用 ALTER TABLE 命令禁用行安全性。禁用行安全不会删除表上定义的任何策略;他们只是被忽略了。然后表中的所有行都是可见和可修改的,受标准 SQL 权限系统的约束。
下面是一个更大的示例,说明如何在生产环境中使用此功能。表 passwd 模拟 Unix 密码文件:
CREATE TABLE passwd (
user_name text UNIQUE NOT NULL,
pwhash text,
uid int PRIMARY KEY,
gid int NOT NULL,
real_name text NOT NULL,
home_phone text,
extra_info text,
home_dir text NOT NULL,
shell text NOT NULL
);
CREATE ROLE admin;
CREATE ROLE bob;
CREATE ROLE alice;
INSERT INTO passwd VALUES
('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
INSERT INTO passwd VALUES
('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
INSERT INTO passwd VALUES
('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');
ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;
CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
CREATE POLICY all_view ON passwd FOR SELECT USING (true);
CREATE POLICY user_mod ON passwd FOR UPDATE
USING (current_user = user_name)
WITH CHECK (
current_user = user_name AND
shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh')
);
GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
GRANT SELECT
(user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)
ON passwd TO public;
GRANT UPDATE
(pwhash, real_name, home_phone, extra_info, shell)
ON passwd TO public;
测试:
set role admin;
table passwd;
set role alice;
table passwd;
select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd;
update passwd set user_name = 'joe';
update passwd set real_name = 'Alice Doe';
update passwd set real_name = 'John Doe' where user_name = 'admin';
update passwd set shell = '/bin/xx';
delete from passwd;
insert into passwd (user_name) values ('xxx');
update passwd set pwhash = 'abc';
迄今为止构建的所有策略都是许可策略,这意味着当应用多个策略时,它们会使用“OR”布尔运算符进行组合。虽然可以将许可策略构建为仅允许在预期情况下访问行,但将许可策略与限制策略(记录必须通过并使用“AND”布尔运算符组合)组合起来会更简单。在上面的示例的基础上,我们添加了一个限制性策略,要求管理员通过本地 Unix 套接字连接以访问 passwd 表的记录:
CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO admin
USING (pg_catalog.inet_client_addr() IS NULL);
然后我们可以看到,由于限制性策略,通过网络连接的管理员将看不到任何记录:(连接时带上 -h 127.0.0.1)
set role admin;
SELECT current_user;
select inet_client_addr();
TABLE passwd;
UPDATE passwd set pwhash = NULL;
引用完整性检查(例如唯一或主键约束和外键引用)始终绕过行安全性以确保维护数据完整性。在开发模式和行级策略时必须小心,以避免通过此类参照完整性检查“隐蔽通道”信息泄漏。
在某些情况下,确保未应用行安全性很重要。例如,在进行备份时,如果行安全性默默地导致某些行从备份中被忽略,这可能是灾难性的。在这种情况下,您可以将 row_security 配置参数设置为 off。这本身并没有绕过行安全性;如果任何查询的结果被策略过滤,它的作用是抛出错误。然后可以调查并修复错误的原因。