受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

39.5. 规则和权限 #

由于PostgreSQL规则系统会重写查询,因此可能访问原始查询中并未使用的其他表或视图。使用更新规则时,这甚至可能包括对表的写访问。

重写规则没有单独的所有者。关系(表或视图)的所有者会自动成为为其定义的重写规则的所有者。PostgreSQL规则系统改变了默认访问控制系统的行为。由于规则而使用的关系都会根据规则所有者的权限进行检查,而不是调用规则的用户。这意味着用户只需要对其查询中明确命名的表/视图具有所需的权限。

例如,某个用户有一份电话号码列表,其中一部分是私人的,另一部分对办公室助理有用。该用户可以这样构造:

CREATE TABLE phone_data (person text, phone text, private boolean);
CREATE VIEW phone_number AS
    SELECT person, CASE WHEN NOT private THEN phone END AS phone
    FROM phone_data;
GRANT SELECT ON phone_number TO assistant;

除了该用户本人(以及数据库超级用户)之外,没有人可以访问phone_data表。但由于GRANT的存在,助理可以对phone_number视图执行SELECT。规则系统会把对phone_numberSELECT重写成对phone_dataSELECT。由于该用户是phone_number的所有者,因此也是规则的所有者,对phone_data的读访问会按照该用户的权限进行检查,于是查询被允许。同时,对phone_number本身的访问检查仍会执行,但这是针对调用用户进行的,因此除了用户本人和助理之外,没有其他人能使用它。

权限是逐条规则检查的。因此,目前助理是唯一能看到公开电话号码的人。但助理还可以再建立一个视图,并把该视图授予公众访问。这样,任何人都可以通过助理的视图看到phone_number中的数据。助理做不到的是创建一个直接访问phone_data的视图。(其实助理可以创建,但它不会起作用,因为每次访问都会在权限检查时被拒绝。)而且,一旦用户发现助理开放了其phone_number视图,用户就可以撤销助理的访问权限。那样一来,对助理视图的任何访问都会立即失败。

这种逐条规则的检查看起来似乎像是一个安全漏洞,但实际上并不是。因为即使不这样工作,助理也可以建立一个与phone_number具有相同列的表,每天把数据复制进去。那样一来,这些就是助理自己的数据,助理同样可以把访问权限授予任何人。GRANT的含义本来就是我信任你。如果某个你信任的人做了上面的事,那么该重新考虑这份信任,并使用REVOKE了。

需要注意的是,虽然视图可以用前文展示的技术来隐藏某些列的内容,但除非设置了security_barrier标志,否则它们不能被用来可靠地隐藏不可见行中的数据。例如,下面这个视图就是不安全的:

CREATE VIEW phone_number AS
    SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';

这个视图看起来似乎很安全,因为规则系统会把对phone_number的任何SELECT重写为对phone_dataSELECT,并附加条件,要求只处理phone不以 412 开头的项。但是,如果用户可以创建自己的函数,就不难让规划器在NOT LIKE表达式之前先执行用户定义函数。例如:

CREATE FUNCTION tricky(text, text) RETURNS bool AS $$
BEGIN
    RAISE NOTICE '% => %', $1, $2;
    RETURN true;
END;
$$ LANGUAGE plpgsql COST 0.0000000000000000000001;

SELECT * FROM phone_number WHERE tricky(person, phone);

phone_data表中的每个人和电话号码都会被打印为一条NOTICE,因为规划器会选择先执行廉价的tricky函数,再执行更昂贵的NOT LIKE。即使禁止用户定义新函数,内置函数也可以用于类似攻击。(例如,大多数类型转换函数都会在其错误消息中包含输入值。)

类似的考虑也适用于更新规则。在上一节的示例中,示例数据库中那些表的所有者可以把shoelace视图上的SELECTINSERTUPDATEDELETE权限授予其他用户,但对shoelace_log只授予SELECT权限。写日志记录的规则动作仍会成功执行,因此其他用户可以看到日志记录。但他们不能伪造记录,也不能操纵或删除已有记录。在这个例子里,不存在通过说服规划器改变操作顺序来破坏规则的可能性,因为唯一引用shoelace_log的规则是一条无条件的INSERT。在更复杂的场景中,情况未必如此。

当视图需要提供行级安全时,应当给它设置security_barrier属性。这样可以防止恶意选择的函数和操作符在视图完成自己的工作之前就接触到行值。例如,如果前文展示的视图按下面方式创建,它就是安全的:

CREATE VIEW phone_number WITH (security_barrier) AS
    SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';

使用security_barrier创建的视图,其性能可能远差于未使用该选项的视图。一般来说,这无法避免:如果最快的候选计划可能损害安全性,那它就必须被拒绝。因此,这个选项默认并不启用。

在处理没有副作用的函数时,查询规划器有更大的灵活性。这类函数称为LEAKPROOF,其中包括许多简单且常用的操作符,例如大量等值操作符。规划器可以安全地允许这类函数在查询执行过程中的任意位置求值,因为即便把它们应用到用户不可见的行上,也不会泄露这些不可见行的信息。此外,不带参数的函数,或者没有从安全屏障视图接收任何参数的函数,即使未被标记为LEAKPROOF,也可以下推,因为它们根本不会接收到来自视图的数据。相反,那些可能根据参数值抛出错误的函数(例如在发生溢出或除零时抛错的函数)就不是LEAKPROOF;如果在安全视图的行过滤之前对其求值,就可能泄露关于不可见行的重要信息。

例如,对于安全屏障视图(或定义了行级安全策略的表)上的查询,如果WHERE子句中使用的某个操作符属于该索引的操作符族,但其底层函数未被标记为LEAKPROOF,那么就不能选择索引扫描。psql程序的元命令\dAo+可用于列出操作符族,并判断其中哪些操作符被标记为LEAKPROOF

需要理解的一点是,即使一个视图是用security_barrier选项创建的,它的安全性也只限于这样一种意义:不可见元组的内容不会被传递给可能不安全的函数。用户仍然可能通过其他方式推断不可见数据;例如,他们可以使用EXPLAIN查看查询计划,或者测量针对该视图执行查询所需的时间。恶意攻击者或许能够推断出不可见数据的大致数量,甚至获得有关数据分布或最常见值的一些信息(因为这些因素可能影响计划运行时间,甚至影响计划的选择,因为它们同样反映在优化器统计信息中)。如果这类“隐通道”攻击值得担心,那么向任何人授予这类数据的访问权限本身就可能是不明智的。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。