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() 或实际连接测试复验,避免因继承链未断而误判。

PostgreSQL 角色权限继承与对象权限审计视图 alice (LOGIN) 业务用户 · INHERIT app_readonly (NOLOGIN) 组角色 · 权限容器 reporting_base (NOLOGIN) 基础只读组 成员 成员 public.orders (表) SELECT / INSERT / UPDATE public.fn_report() (函数) EXECUTE public.seq_order (序列) USAGE / SELECT GRANT SELECT GRANT EXECUTE GRANT USAGE PUBLIC 角色风险 · 函数默认 EXECUTE · public 模式可能可 CREATE · 默认权限未收紧 隐式授权 最小化审计结论 alice 实际可 UPDATE orders 来源:app_readonly 继承 动作:REVOKE UPDATE 复验:has_table_privilege 核对 审计判断链: 1. 展开 pg_auth_members 继承树 → 2. 用 has_table_privilege() 验证有效权限 → 3. 对比业务角色矩阵找差集 4. 定位过度授权来源(直接 / 继承 / PUBLIC / 默认权限) → 5. 选择最小撤销路径 → 6. 复验并记录审计证据 高风险信号:admin_option=true、PUBLIC 持有 EXECUTE、public 模式 CREATE 未撤销、默认权限过宽

可直接使用的审计检查清单

检查项查询/命令风险判断
角色继承链pg_auth_members存在 admin_option=true 需重点核查
表有效权限has_table_privilege()只读角色出现 UPDATE/DELETE 即过度授权
PUBLIC 函数权限pg_proc.proaclacl 为 NULL 表示默认 PUBLIC EXECUTE
默认权限pg_default_acl未来对象自动授权过宽
模式 CREATEhas_schema_privilege()PUBLIC 可 CREATE 应撤销
审计中常见的误判是只查看 information_schema.role_table_grants 就下结论。该视图不包含继承和 PUBLIC 带来的权限,必须结合 has_*_privilege() 函数做有效权限验证,否则会漏掉真实存在的越权路径。

权限最小化不是一次性动作,而是持续过程。建议将上述查询固化为定期审计脚本,输出“角色—对象—有效权限—来源—是否合规”的结果集,并对不合规项生成 REVOKE 建议。每次变更后重新执行验证函数,确保撤销生效且未影响正常业务。只有把继承链、对象权限、默认权限和 PUBLIC 风险放在同一张审计视图中核对,才能真正回答“谁最终能访问什么”这一核心问题。