↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5

CREATE POLICY

CREATE POLICY — 为一个表定义一条新的行级安全策略

大纲

CREATE POLICY name ON table_name
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( using_expression ) ]
    [ WITH CHECK ( check_expression ) ]

描述

CREATE POLICY 命令为一个表定义一条新的行级安全策略。请注意,必须先在该表上启用行级安全(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY),已创建的策略才会被应用。

策略允许对符合相关策略表达式的行进行选择、插入、更新或删除。现有表行会根据 USING 中指定的表达式进行检查,而将通过 INSERT 或 UPDATE 创建的新行则会根据 WITH CHECK 中指定的表达式进行检查。当 USING 表达式对给定行返回真时,该行对用户可见;如果返回假或 null,则该行不可见。通常,当某行不可见时不会报错,但也有例外,见表 300。当 WITH CHECK 表达式对某行返回真时,该行会被插入或更新;如果返回假或 null,则会报错。

对于 INSERT、UPDATE 和 MERGE 语句,WITH CHECK 表达式会在 BEFORE 触发器触发后、在进行任何实际数据修改之前强制执行。因此,BEFORE ROW 触发器可以修改待插入的数据,从而影响安全策略检查的结果。WITH CHECK 表达式会在任何其他约束之前执行。

策略名称按表区分。因此,同一个策略名可以用于许多不同的表,并且在每个表上都可以有适合该表的定义。

策略可以针对特定命令或特定角色应用。除非另有指定,新建策略默认适用于所有命令和角色。多个策略可以应用于同一命令;更多细节见下文。表 300 总结了不同类型的策略如何应用于特定命令。

对于既可以具有 USING 又可以具有 WITH CHECK 表达式的策略(ALL 和 UPDATE),如果未定义 WITH CHECK 表达式,那么 USING 表达式将同时用于决定哪些行可见(普通 USING 情形)以及允许写入哪些新行(WITH CHECK 情形)。

如果某个表启用了行级安全,但不存在适用的策略,则会假定存在一条“默认拒绝”策略,因此没有任何行可见或可更新。

参数

name #

要创建的策略名称。它必须不同于该表上任何其他策略的名称。

table_name #

该策略适用的表的名称(可选模式限定)。

PERMISSIVE #

指定将该策略创建为宽松策略。适用于给定查询的所有宽松策略都会使用布尔“OR”操作符组合在一起。通过创建宽松策略,管理员可以扩大可访问行的集合。策略默认是宽松的。

RESTRICTIVE #

指定将该策略创建为限制性策略。适用于给定查询的所有限制性策略都会使用布尔“AND”操作符组合在一起。通过创建限制性策略,管理员可以缩小可访问行的集合,因为每一行都必须通过所有限制性策略。

请注意,要让限制性策略能够有效缩小访问范围,必须先至少有一条宽松策略授予对行的访问。如果只存在限制性策略,则没有任何行可访问。当宽松策略和限制性策略混合存在时,只有在至少一条宽松策略通过且所有限制性策略也都通过时,某一行才可访问。

command #

该策略适用的命令。有效选项是 ALL、SELECT、INSERT、UPDATE 和 DELETE。ALL 是默认值。有关这些策略如何应用的细节见下文。

role_name #

该策略适用的角色(或多个角色)。默认是 PUBLIC,即对所有角色应用该策略。

using_expression #

任意返回 boolean 的 SQL 条件表达式。该条件表达式不能包含任何聚合函数或窗口函数。如果启用了行级安全,该表达式会被添加到引用该表的查询中。表达式返回真的行将对用户可见。表达式返回假或 null 的行在 SELECT 中对用户不可见,在 UPDATE 或 DELETE 中也不能用于修改。通常,这类行会被静默忽略,不会报告错误(但例外情况见表 300)。

check_expression #

任意返回 boolean 的 SQL 条件表达式。该条件表达式不能包含任何聚合函数或窗口函数。如果启用了行级安全,该表达式将用于针对该表的 INSERT 和 UPDATE 查询。只有使该表达式求值为真的行才会被允许。对任何被插入的行,或更新后产生的任何行,如果该表达式求值为假或 null,就会抛出错误。请注意,check_expression 是针对该行拟写入的新内容而不是原始内容求值的。

针对每种命令的策略

ALL #

对策略使用 ALL 意味着无论命令类型如何,该策略都适用于所有命令。如果存在 ALL 策略且还存在更具体的策略,则 ALL 策略和更具体的策略(或多个策略)都会被应用。此外,ALL 策略会同时应用于查询的选择端和修改端,并在两端使用 USING 表达式(如果只定义了 USING 表达式)。

例如,如果发出 UPDATE,那么 ALL 策略既适用于 UPDATE 能够选出作为更新目标的行(应用 USING 表达式),也适用于更新后的结果行,以检查它们是否允许被写入该表(若定义了 WITH CHECK 表达式则应用之,否则应用 USING 表达式)。如果 INSERT 或 UPDATE 命令试图向表中添加未通过 ALL 策略的 WITH CHECK 表达式(若未定义 WITH CHECK 表达式,则为其 USING 表达式)的行,整个命令将被中止。

SELECT #

对策略使用 SELECT,意味着它适用于 SELECT 查询,以及在其定义所在关系上需要 SELECT 权限的任何场景。结果是,只有通过 SELECT 策略的那些行才会在 SELECT 查询中返回,而像 UPDATE、DELETE 和 MERGE 这类需要 SELECT 权限的查询,也只能看到 SELECT 策略允许的那些行。SELECT 策略不能带有 WITH CHECK 表达式,因为它只适用于从关系中取回行的情况,下述情形除外。

如果某个修改数据的查询带有 RETURNING 子句,则该关系上需要 SELECT 权限,并且该关系中新插入或更新的任何行都必须满足该关系的 SELECT 策略,才能提供给 RETURNING 子句。如果新插入或更新的行不满足该关系的 SELECT 策略,就会抛出错误(需要返回的插入或更新行绝不会被静默忽略)。

如果 INSERT 带有 ON CONFLICT DO UPDATE 子句,或带有指定仲裁索引或约束的 ON CONFLICT DO NOTHING 子句,那么该关系上也需要 SELECT 权限,并且提议插入的行会按照该关系的 SELECT 策略进行检查。如果某个提议插入的行不满足该关系的 SELECT 策略,就会抛出错误(INSERT 绝不会被静默跳过)。此外,如果走的是 UPDATE 路径,则待更新的行以及更新后的新行都会按照该关系的 SELECT 策略进行检查;如果不满足,同样会报错(辅助的 UPDATE 绝不会被静默跳过)。

MERGE 命令要求在源关系和目标关系上都具有 SELECT 权限,因此每个关系的 SELECT 策略都会在连接它们之前被应用,而 MERGE 动作也只能看到这些策略允许的行。另外,如果执行了 UPDATE 动作,则目标关系的 SELECT 策略会像独立的 UPDATE 那样应用到更新后的行,但不同之处在于,不满足这些策略时会报错。

INSERT #

对策略使用 INSERT,意味着它将适用于 INSERT 命令,以及包含 INSERT 动作的 MERGE 命令。插入的行如果未通过该策略,将导致策略违规错误,并且整个 INSERT 命令将被中止。INSERT 策略不能带有 USING 表达式,因为它只适用于向关系添加行的情况。

注意,带有 ON CONFLICT DO NOTHING/UPDATE 子句的 INSERT,会对所有提议插入的行检查 INSERT 策略的 WITH CHECK 表达式,无论这些行最终是否真的被插入。

UPDATE #

对策略使用 UPDATE,意味着它将适用于 UPDATE、SELECT FOR UPDATE、SELECT FOR SHARE 命令,以及 INSERT 命令中辅助的 ON CONFLICT DO UPDATE 子句,还适用于包含 UPDATE 动作的 MERGE 命令。由于 UPDATE 命令需要取出现有行并用修改后的新行替换它,因此 UPDATE 策略同时接受 USING 表达式和 WITH CHECK 表达式。USING 表达式决定 UPDATE 命令能够看到哪些行来执行操作,而 WITH CHECK 表达式则定义哪些修改后的行允许被写回该关系。

任何更新后的值未通过 WITH CHECK 表达式的行都会导致错误,并且整个命令将被中止。如果只指定了一个 USING 子句,那么该子句将被用于 USING 和 WITH CHECK 两种情况。

通常,UPDATE 命令还需要从待更新关系的列中读取数据(例如在 WHERE 子句、RETURNING 子句,或 SET 子句右侧的表达式中)。这种情况下,正在被更新的关系上也需要 SELECT 权限,并且除了 UPDATE 策略外,还会应用适当的 SELECT 或 ALL 策略。这样,用户除了必须通过 UPDATE 或 ALL 策略获准更新这些行之外,还必须通过 SELECT 或 ALL 策略访问正在被更新的行。

当 INSERT 命令带有辅助的 ON CONFLICT DO UPDATE 子句时,如果走 UPDATE 路径,则待更新的行会先根据各适用的 UPDATE 策略的 USING 表达式进行检查,然后新的更新后行会再根据 WITH CHECK 表达式进行检查。但要注意,与独立的 UPDATE 命令不同,如果现有行没有通过 USING 表达式检查,就会报错(UPDATE 路径绝不会被静默跳过)。对 MERGE 命令中的 UPDATE 动作也是如此。

DELETE #

对策略使用 DELETE,意味着它将适用于 DELETE 命令,以及包含 DELETE 动作的 MERGE 命令。对于 DELETE 命令,只有通过该策略的行才会被 DELETE 命令看到。可能存在一些通过 SELECT 策略可见,但由于未通过 DELETE 策略的 USING 表达式而不能删除的行。但请注意,在 MERGE 命令中的 DELETE 动作会看到通过 SELECT 策略可见的行;如果某一行未通过 DELETE 策略,就会报错。

在多数情况下,DELETE 命令也需要从其删除所针对的关系中的列读取数据(例如在 WHERE 子句或 RETURNING 子句中)。这种情况下,该关系上也需要 SELECT 权限,并且除了 DELETE 策略外,还会应用适当的 SELECT 或 ALL 策略。这样,用户除了必须通过 DELETE 或 ALL 策略获准删除这些行之外,还必须通过 SELECT 或 ALL 策略访问正在被删除的行。

DELETE 策略不能具有 WITH CHECK 表达式,因为它只适用于正在从关系中删除行的情况,所以没有新行需要检查。

表 300 总结了不同类型的策略如何应用到特定命令上。在该表中,“检查”表示要检查策略表达式,若其返回假或 null 则抛出错误;而“筛选”表示若策略表达式返回假或 null,则该行会被静默忽略。

表 300. 按命令类型应用的策略

命令SELECT/ALL策略INSERT/ALL策略UPDATE/ALL策略DELETE/ALL策略
USING 表达式WITH CHECK 表达式USING 表达式WITH CHECK 表达式USING 表达式
SELECT / COPY ... TO筛选现有行————
SELECT FOR UPDATE/SHARE筛选现有行—筛选现有行——
INSERT 检查新行 [a] 检查新行———
UPDATE 筛选现有行 [a] 并检查新行 [a] —筛选现有行检查新行—
DELETE 筛选现有行 [a] ———筛选现有行
INSERT ... ON CONFLICT 检查新行 [b][c] 检查新行 [c] ———
ON CONFLICT DO UPDATE 检查现有行和新行 [d] —检查现有行 检查新行 [d] —
MERGE筛选源行和目标行————
MERGE ... THEN INSERT 检查新行 [a] 检查新行———
MERGE ... THEN UPDATE检查新行—检查现有行检查新行—
MERGE ... THEN DELETE————检查现有行

[a] 如果需要读取现有行或新行,例如在 WHERE 或 RETURNING 子句引用该关系的列时。

[b] 如果指定了仲裁索引或约束。

[c] 无论最终是否实际发生冲突,提议插入的行都会被检查。

[d] 指辅助 UPDATE 命令中的新行,它可能与原始 INSERT 命令中的新行不同。


多条策略的应用

当不同命令类型的多条策略应用于同一命令时(例如 SELECT 和 UPDATE 策略应用于 UPDATE 命令),用户必须同时具有这两种权限(例如既有从该关系中选取行的权限,也有更新这些行的权限)。因此,一种策略类型的表达式会与另一种策略类型的表达式使用 AND 操作符组合。

当同一命令类型的多条策略应用于同一命令时,必须至少有一条 PERMISSIVE 策略授予对该关系的访问权,并且所有 RESTRICTIVE 策略都必须通过。因此,所有 PERMISSIVE 策略表达式使用 OR 组合,所有 RESTRICTIVE 策略表达式使用 AND 组合,然后再将两者的结果使用 AND 组合。如果没有 PERMISSIVE 策略,则访问被拒绝。

请注意,就组合多条策略而言,ALL 策略会被视为与当前正在应用的其他策略同一类型。

例如,对于一个同时需要 SELECT 和 UPDATE 权限的 UPDATE 命令,如果这两类策略各自都有多个适用项,它们会按以下方式组合:

RESTRICTIVE SELECT/ALL 策略 1 中的 表达式
AND
RESTRICTIVE SELECT/ALL 策略 2 中的 表达式
AND
...
AND
(
  PERMISSIVE SELECT/ALL 策略 1 中的 表达式
  OR
  PERMISSIVE SELECT/ALL 策略 2 中的 表达式
  OR
  ...
)
AND
RESTRICTIVE UPDATE/ALL 策略 1 中的 表达式
AND
RESTRICTIVE UPDATE/ALL 策略 2 中的 表达式
AND
...
AND
(
  PERMISSIVE UPDATE/ALL 策略 1 中的 表达式
  OR
  PERMISSIVE UPDATE/ALL 策略 2 中的 表达式
  OR
  ...
)

注解

要为一个表创建或修改策略,你必须是该表的拥有者。

虽然策略会应用于针对数据库中表的显式查询,但当系统执行内部引用完整性检查或验证约束时,并不会应用这些策略。这意味着仍然存在间接判断某个给定值是否存在的方法。一个例子是,尝试向某个主键列或带有唯一约束的列插入重复值。如果插入失败,用户就能推断该值已经存在。(这个例子假定策略允许该用户插入自己无权看见的行。)另一个例子是,用户被允许向一个引用了另一张表的表中插入数据,而被引用的那张表本身对其是隐藏的。用户可以通过向引用表插入值来判断其存在性;插入成功就表示该值存在于被引用表中。要解决这些问题,可以仔细设计策略,防止用户插入、删除或更新那些可能暗示其本来无权看见的值是否存在的行,或者改用生成的值(例如代理键)来代替具有外部含义的键。

通常,为了防止受保护的数据无意间暴露给可能不可信的用户定义函数,系统会在应用用户查询中出现的条件之前,先强制执行安全策略施加的过滤条件。不过,被系统(或系统管理员)标记为 LEAKPROOF 的函数和操作符由于被假定为可信,可以在策略表达式之前求值。

由于策略表达式会被直接添加到用户查询中,因此它们将以执行整个查询的用户权限运行。因此,使用某条策略的用户必须能够访问表达式中引用的所有表或函数,否则在尝试查询启用了行级安全的表时,只会收到权限被拒绝的错误。不过,这并不改变视图的工作方式。与普通查询和视图一样,被视图引用的表的权限检查和策略将使用视图所有者的权限,以及适用于视图所有者的任何策略;但如果视图是用 security_invoker 选项定义的,则例外(见 CREATE VIEW)。

对于 MERGE,不存在单独的策略。相反,执行 MERGE 时会根据实际执行的动作,应用为 SELECT、INSERT、UPDATE 和 DELETE 定义的策略。

更多讨论和实际示例见第 5.9 节。

兼容性

CREATE POLICY 是一种 PostgreSQL 扩展。

报告文档问题

阅读 上游文档. 通过 PostgreSQL 文档反馈表单.