跳转到主要内容

55 角色与 GRANT

向对象授予权限,并使用普通数据库角色演示行级安全。

PostgreSQL 用一个统一的概念——角色——表示用户和组。角色可以登录(使用 LOGIN),可以被授予给其他角色(实现类似组的行为),可以拥有对象,也可以获得授予给它的权限。GRANT 添加权限;REVOKE 移除权限。每个数据库对象都有一个拥有全部权限的所有者;其他人都需要获得明确的授权。这里的原则是最小权限:每个角色只获得它所需的权限。

本章要点

  • 创建带登录和不带登录的角色
  • 在模式、表和列上授予与撤销权限
  • 模式上的 USAGE 权限(经常被忘记)
  • 为将来创建的对象设置 DEFAULT PRIVILEGES
  • 行级安全(RLS)简介

示例

示例 55.1 角色与 GRANT
-- Inspect the current session
SELECT current_user, session_user;

-- Create ordinary roles (requires CREATEROLE or superuser privileges)
-- CREATE ROLE app_read NOLOGIN;                                 -- a "group" role
-- CREATE ROLE alice LOGIN PASSWORD 'change-me' IN ROLE app_read;
-- CREATE ROLE app_write NOLOGIN;

-- Grant access to a schema and read on tables
-- GRANT USAGE  ON SCHEMA public TO app_read;
-- GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_read;

-- Future tables in this schema also get SELECT for app_read
-- ALTER DEFAULT PRIVILEGES IN SCHEMA public
--   GRANT SELECT ON TABLES TO app_read;

-- Write-level
-- GRANT app_read TO app_write;                          -- inheritance: app_write also reads
-- GRANT INSERT, UPDATE, DELETE ON fruits TO app_write;

-- Revoke
-- REVOKE INSERT ON fruits FROM app_write;

-- Inspect your privileges on a table
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'fruits' AND grantee = current_user;

-- Row-level security: per-row visibility decided by a policy
-- (demo on a separate table so we don't change `fruits` permissions)
DROP TABLE IF EXISTS tenant_docs;
CREATE TABLE tenant_docs (
  id       integer PRIMARY KEY,
  tenant   text NOT NULL,
  content  text NOT NULL
);
INSERT INTO tenant_docs VALUES (1, 'acme', 'hello'), (2, 'globex', 'world');

ALTER TABLE tenant_docs ENABLE ROW LEVEL SECURITY;
-- Apply the policy to the table owner as well (superusers/BYPASSRLS still bypass it).
ALTER TABLE tenant_docs FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON tenant_docs
  USING (tenant = current_setting('app.tenant', true));

-- Set the tenant for the session and try a select
SET app.tenant TO 'acme';
SELECT * FROM tenant_docs;        -- only sees the 'acme' row

SET app.tenant TO 'globex';
SELECT * FROM tenant_docs;        -- only sees the 'globex' row

-- Cleanup
DROP POLICY IF EXISTS tenant_isolation ON tenant_docs;
DROP TABLE IF EXISTS tenant_docs;

表权限包括 SELECT、INSERT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER,以及 PostgreSQL 17 中的 MAINTAIN。模式上最重要的权限是 USAGE(允许角色引用其中的对象)和 CREATE(允许角色创建新对象)。数据库上的 CONNECT、CREATE 和 TEMPORARY 也很重要。

表上的权限不会从模式级授权中继承。要使用模式 public 中的表,角色需要在 public 上拥有 USAGE,并且在该表上拥有某项权限(例如 SELECT)。缺少模式级 USAGE 时,就会出现常见的“permission denied for schema public”错误。

DEFAULT PRIVILEGES 解决了“新表没有授权”的问题:角色以后创建的任何对象都会自动继承配置好的授权。这些配置只适用于指定的创建角色所创建的对象,不会自动适用于每个角色创建的对象。对于会创建表的每个迁移/所有者角色,都要分别进行配置。

行级安全(RLS)是一个更高级的功能:你可以在表上启用 ENABLE ROW LEVEL SECURITY,并编写根据谓词过滤行的 POLICY。启用 RLS 后,如果当前角色受该策略约束,类似 SELECT * FROM tenant_docs 的查询只会返回当前角色或设置所允许看到的行。可以自由修改的 app.tenant 设置只是演示;真实应用必须阻止不受信任的客户端选择其他租户。

运行示例

运行示例时,请使用一个不具备超级用户权限、没有 BYPASSRLS 属性,并且能够在练习数据库中创建表的角色。FORCE ROW LEVEL SECURITY 会让这张表的所有者也遵守策略。超级用户仍然可以看到两个租户。带注释的角色管理命令需要相应的角色管理权限。

$ psql -f source/roles-and-grant.sql postgres
 current_user | session_user
--------------+--------------
 your_user    | your_user
(1 row)
 ...

常见陷阱

  • 忘记 GRANT USAGE ON SCHEMA public。角色可能已经对每张表都拥有 SELECT,但仍然无法查询,因为它缺少模式级的使用权限。
  • 只为现有表授予权限,却没有配置 DEFAULT PRIVILEGES——应用创建的每张新表,读角色都无法访问。
  • 将 SECURITY DEFINER 函数与宽松的 search_path 混用,会打开权限提升的通道。此类函数应始终设置 search_path。
  • 在 CREATE ROLE ... PASSWORD 'plain' 中存储密码,对个人数据库来说可以接受;生产环境应使用 SCRAM 身份验证,绝不要在 SQL 脚本中硬编码密码。
提示

一个常见模式是使用三个角色:app_owner(拥有对象,用于迁移)、app_read(供分析和副本使用的只读角色)以及 app_write(供应用使用的读写角色)。让 app_owner 拥有对象,再向其他角色授予权限。

动手练习

使用超级用户权限创建一个角色,只向它授予 fruits 上的 SELECT。然后以该角色重新连接(psql -U newrole),确认可以读取但不能写入。接着试验 RLS:在一张表上启用它,添加策略,并观察 SET 改变后 SELECT 的结果如何变化。

下载

SQL 文件:roles-and-grant.sql · 全部示例文件 · 准备工作