PostgreSQL 的 CREATEROLE 权限长期以来是个"准超级用户"——拥有它的人几乎可以做任何事:创建任意角色、给自己授予新角色的成员身份、继承新角色的全部权限。这条从角色创建到权限膨胀的路径,在 PG16 被正式切断。核心手段之一就是新增的 GUC 参数 createrole_self_grant。
CREATEROLE 为什么危险
在 PG15 及更早版本中,一个只有 CREATEROLE 权限(没有 SUPERUSER)的用户可以做这样的事:
-- 假设当前用户 app_admin 只有 CREATEROLE,没有 SUPERUSER
CREATE ROLE powerful_role INHERIT LOGIN;
-- 创建者自动成为 powerful_role 的成员,并自动继承其权限
创建角色的那一刻,创建者自动获得对新角色的 INHERIT 和 SET 成员属性。这意味着:
- 如果
powerful_role后来被超级用户授予了对某张敏感表的访问权,app_admin会自动继承这个访问权,无需任何额外 GRANT 操作。 app_admin可以通过SET ROLE powerful_role以该角色身份执行操作,绕过原本的身份约束。
这本质上是一个权限 escalator:CREATEROLE 的持有者不需要超级用户权限,就能通过不断创建角色来逐步膨胀自己的权限集合。在多租户环境或受管数据库服务中,这尤其让人头疼。
createrole_self_grant 的机制
PG16 引入了 createrole_self_grant 参数,精确控制"创建角色时,创建者自动获得什么"。它的取值是 SET、INHERIT 的组合,或空字符串:
| 取值 | 效果 |
|---|---|
| 空(默认) | 创建者不自动获得任何成员属性 |
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 时,建议走以下步骤:
- 盘点 CREATEROLE 用户——用上面那条查询找出所有非超级用户的 CREATEROLE 持有者。
- 审计隐式依赖——检查是否有应用代码假设"创建角色 = 自动获得成员身份"。这类代码在 PG16 下会悄无声息地失去权限。
- 按角色设置
createrole_self_grant——只对确实需要旧行为的角色放开SET, INHERIT,其余保持默认空值。 - 补上显式 GRANT——在创建角色的流程中,加入明确的
GRANT new_role TO creator语句,替代原来的隐式行为。 - 验证审计链——确认
pg_auth_members中所有成员关系都有对应的显式 GRANT 记录,没有遗漏。
createrole_self_grant 的设计思路值得注意:它没有彻底禁止 CREATEROLE 给自己授权(那会破坏合理的业务场景),而是把"自动获得"变成了"必须显式操作"。显式操作可审计、可撤销、可控制。在安全设计中,把隐式行为变成显式行为,往往比彻底禁止更实用。