PostgreSQL 16 的 createrole_self_grant:给 CREATEROLE 权力画一条边界

2026-06-03 21 预计阅读时间:1 分钟
来源:postgr.es AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:7 分钟

PostgreSQL 的 CREATEROLE 权限长期以来是个"准超级用户"——拥有它的人几乎可以做任何事:创建任意角色、给自己授予新角色的成员身份、继承新角色的全部权限。这条从角色创建到权限膨胀的路径,在 PG16 被正式切断。核心手段之一就是新增的 GUC 参数 createrole_self_grant

CREATEROLE 为什么危险

在 PG15 及更早版本中,一个只有 CREATEROLE 权限(没有 SUPERUSER)的用户可以做这样的事:

-- 假设当前用户 app_admin 只有 CREATEROLE,没有 SUPERUSER
CREATE ROLE powerful_role INHERIT LOGIN;
-- 创建者自动成为 powerful_role 的成员,并自动继承其权限

创建角色的那一刻,创建者自动获得对新角色的 INHERITSET 成员属性。这意味着:

  • 如果 powerful_role 后来被超级用户授予了对某张敏感表的访问权,app_admin 会自动继承这个访问权,无需任何额外 GRANT 操作。
  • app_admin 可以通过 SET ROLE powerful_role 以该角色身份执行操作,绕过原本的身份约束。

这本质上是一个权限 escalator:CREATEROLE 的持有者不需要超级用户权限,就能通过不断创建角色来逐步膨胀自己的权限集合。在多租户环境或受管数据库服务中,这尤其让人头疼。

createrole_self_grant 的机制

PG16 引入了 createrole_self_grant 参数,精确控制"创建角色时,创建者自动获得什么"。它的取值是 SETINHERIT 的组合,或空字符串:

取值 效果
空(默认) 创建者不自动获得任何成员属性
SET 创建者可以 SET ROLE 到新角色,但不继承其权限
INHERIT 创建者自动继承新角色的权限,但不能 SET ROLE
SET, INHERIT 同时获得 SET 和 INHERIT(等同于 PG15 的旧行为)

PG16 的默认值是空字符串——创建者不再自动获得任何东西。这是最安全的默认姿态。

参数可以在多个层级设置:

-- 全局级别(需要超级用户)
ALTER SYSTEM SET createrole_self_grant = 'set';
SELECT pg_reload_conf();

-- 数据库级别
ALTER DATABASE mydb SET createrole_self_grant = 'set';

-- 角色级别(只影响该角色创建新角色时的行为)
ALTER ROLE app_admin SET createrole_self_grant = 'set';

角色级别的设置尤其有用——你可以让不同的 CREATEROLE 用户有不同的自授予策略,而不需要一刀切。

实际操作:从升级到配置

如果你的系统从 PG15 升级到 PG16,createrole_self_grant 默认为空,旧行为会立刻消失。依赖"创建者自动继承"的逻辑会静默失败。下面是一个完整的检查与迁移流程:

# 1. 升级前,找出所有拥有 CREATEROLE 的非超级用户角色
psql -c "
SELECT r.rolname, r.rolcreaterole, r.rolsuper
FROM pg_roles r
WHERE r.rolcreaterole AND NOT r.rolsuper
ORDER BY r.rolname;
"
-- 2. 升级后,检查当前参数值
SHOW createrole_self_grant;

-- 3. 如果业务逻辑依赖旧行为(创建者需要自动继承),逐角色配置
--    只给真正需要的角色放开,不要全局设置
ALTER ROLE tenant_admin SET createrole_self_grant = 'set, inherit';

-- 4. 对于大多数 CREATEROLE 用户,保持默认(空)即可
--    他们创建角色后,需要显式 GRANT 才能获得成员身份:
CREATE ROLE report_reader NOINHERIT NOLOGIN;
-- 以下语句在 PG16 中不再是自动的,必须显式执行
GRANT report_reader TO app_admin;

-- 5. 验证:确认 app_admin 是否真的获得了成员身份
SELECT r.rolname, m.roleid::regrole AS member_of
FROM pg_auth_members m
JOIN pg_roles r ON m.member = r.oid
WHERE r.rolname = 'app_admin';

关键变化在于:GRANT 不再是隐式的。这把权力从"创建者自动获取"变成了"必须显式授予"。即使 CREATEROLE 用户可以执行 GRANT,这个动作也会被记录在 pg_auth_members 中,可审计、可追溯。

PG16 对 CREATEROLE 的其他限制

createrole_self_grant 不是 PG16 唯一的限制。CREATEROLE 用户现在还:

  • 不能创建带有 BYPASSRLS 属性的角色
  • 不能创建带有 REPLICATION 属性的角色
  • 不能给新角色授予自己不具备的权限(之前可以)

这些变化共同把 CREATEROLE 从"准超级用户"拉回到"角色管理员"的位置。它能管理角色结构,但不能凭空创造自己没有的权力。

迁移检查清单

升级到 PG16 或在新部署中配置 CREATEROLE 时,建议走以下步骤:

  1. 盘点 CREATEROLE 用户——用上面那条查询找出所有非超级用户的 CREATEROLE 持有者。
  2. 审计隐式依赖——检查是否有应用代码假设"创建角色 = 自动获得成员身份"。这类代码在 PG16 下会悄无声息地失去权限。
  3. 按角色设置 createrole_self_grant——只对确实需要旧行为的角色放开 SET, INHERIT,其余保持默认空值。
  4. 补上显式 GRANT——在创建角色的流程中,加入明确的 GRANT new_role TO creator 语句,替代原来的隐式行为。
  5. 验证审计链——确认 pg_auth_members 中所有成员关系都有对应的显式 GRANT 记录,没有遗漏。

createrole_self_grant 的设计思路值得注意:它没有彻底禁止 CREATEROLE 给自己授权(那会破坏合理的业务场景),而是把"自动获得"变成了"必须显式操作"。显式操作可审计、可撤销、可控制。在安全设计中,把隐式行为变成显式行为,往往比彻底禁止更实用。


相关推荐