3. PostgreSQL授权模型审计
PostgreSQL 的授权模型与许多商业数据库不同:它没有独立的“用户”和“组”两种实体,而是统一为角色(Role)。一个角色既可以像用户一样登录,也可以像组一样被其他角色继承。理解这一点,是审计权限是否合理、是否存在越权风险的前提。审计的目标不是简单罗列 GRANT 语句,而是回答三个问题:谁最终能访问什么对象、这种能力是通过哪条继承链获得的、是否存在超出业务需要的权限。
角色、用户与组的统一模型
在 PostgreSQL 中,创建角色时通过属性区分其用途。LOGIN 属性使角色可以建立连接,通常代表“用户”;不授予 LOGIN 的角色则常作为“组”使用,用于集中管理权限。例如:
CREATE ROLE app_readonly NOLOGIN;
CREATE ROLE alice LOGIN PASSWORD '***' IN ROLE app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
这里 app_readonly 是权限容器,alice 通过 IN ROLE 成为其成员。审计时必须意识到:角色的实际权限 = 自身直接授予的权限 + 所有可继承成员角色的权限。如果成员关系配置为 INHERIT(默认),成员自动获得父角色权限;若为 NOINHERIT,则需显式 SET ROLE 才能使用。这一差异直接影响“该角色当前是否真的能读某张表”的判断。
权限继承与 SET ROLE 的审计要点
继承关系可能形成多层链:alice → app_readonly → reporting_base。审计时不能只看直接授权,而应展开完整继承树。PostgreSQL 提供 pg_auth_members 记录成员关系,pg_roles 记录角色属性。一个实用的审计查询如下:
SELECT r.rolname AS member, g.rolname AS granted_role,
m.admin_option, r.rolinherit
FROM pg_auth_members m
JOIN pg_roles r ON r.oid = m.member
JOIN pg_roles g ON g.oid = m.roleid
ORDER BY member, granted_role;
结果中若出现 admin_option = true,说明该成员可以把此角色再授予他人,属于高风险管理权限,应重点核查。若 rolinherit = false,则成员默认不继承权限,审计结论需相应调整。
对象权限与 GRANT/REVOKE 审计
对象权限包括表/视图的 SELECT、INSERT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER,序列的 USAGE、SELECT、UPDATE,函数的 EXECUTE,以及模式的 USAGE、CREATE 等。审计应关注两类问题:一是权限过宽,例如业务只读账号却拥有 INSERT/UPDATE/DELETE;二是权限来源不清,例如通过 PUBLIC 或默认权限间接获得。
查看某角色在某表上的有效权限,可使用:
SELECT grantee, privilege_type, is_grantable
FROM information_schema.role_table_grants
WHERE table_schema='public' AND table_name='orders'
ORDER BY grantee, privilege_type;
注意该视图只反映显式授权,不包含通过角色继承或 PUBLIC 获得的权限。因此完整审计需要结合 has_table_privilege() 函数逐角色验证:
SELECT has_table_privilege('alice','public.orders','SELECT') AS can_select,
has_table_privilege('alice','public.orders','UPDATE') AS can_update;
若 can_update 为 true,但业务上 alice 只应只读,则需通过 REVOKE UPDATE ON public.orders FROM alice; 或从父角色中撤销来修复。撤销后应再次调用该函数确认结果,形成“授权—验证—修复—复验”的闭环。
默认权限与 PUBLIC 角色风险
PostgreSQL 默认允许 PUBLIC 在 public 模式中创建对象(取决于版本与配置),并且函数默认对 PUBLIC 具有 EXECUTE 权限。这意味着即使没有显式授权,匿名或低权限角色也可能调用某些函数、访问某些对象。审计时应检查:
public模式的CREATE权限是否已从 PUBLIC 撤销;- 新建函数是否默认开放 EXECUTE,是否需要
REVOKE EXECUTE ON FUNCTION ... FROM PUBLIC; - 是否使用
ALTER DEFAULT PRIVILEGES为新对象设置安全基线。
默认权限审计可查询 pg_default_acl,确认未来创建的表、序列、函数会自动授予哪些角色。若发现默认授予过宽,应通过 ALTER DEFAULT PRIVILEGES FOR ROLE owner IN SCHEMA public REVOKE ... 收紧。
权限最小化审计方法
最小化审计的核心是“以业务需求为基准,反向核对实际权限”。建议按以下顺序执行:先梳理业务角色矩阵(谁需要读、写、执行),再导出实际权限,最后做差集。差集中“实际有、业务不需要”的部分即为过度授权。对于每个过度授权,判断其来源是直接 GRANT、角色继承还是 PUBLIC,再选择最小代价的撤销路径。撤销后必须用 has_*_privilege() 或实际连接测试复验,避免因继承链未断而误判。
可直接使用的审计检查清单
| 检查项 | 查询/命令 | 风险判断 |
|---|---|---|
| 角色继承链 | pg_auth_members | 存在 admin_option=true 需重点核查 |
| 表有效权限 | has_table_privilege() | 只读角色出现 UPDATE/DELETE 即过度授权 |
| PUBLIC 函数权限 | pg_proc.proacl | acl 为 NULL 表示默认 PUBLIC EXECUTE |
| 默认权限 | pg_default_acl | 未来对象自动授权过宽 |
| 模式 CREATE | has_schema_privilege() | PUBLIC 可 CREATE 应撤销 |
information_schema.role_table_grants 就下结论。该视图不包含继承和 PUBLIC 带来的权限,必须结合 has_*_privilege() 函数做有效权限验证,否则会漏掉真实存在的越权路径。权限最小化不是一次性动作,而是持续过程。建议将上述查询固化为定期审计脚本,输出“角色—对象—有效权限—来源—是否合规”的结果集,并对不合规项生成 REVOKE 建议。每次变更后重新执行验证函数,确保撤销生效且未影响正常业务。只有把继承链、对象权限、默认权限和 PUBLIC 风险放在同一张审计视图中核对,才能真正回答“谁最终能访问什么”这一核心问题。