PostgreSQL 权限管理踩坑记:从 42501 到默认权限和 RLS

前段时间给一个新库配只读账号,执行了这么一句:

sql 复制代码
GRANT SELECT ON ALL TABLES IN SCHEMA app TO readonly;

应用连上去还是报 permission denied for schema app。当时还纳闷,表权限都给了,怎么连 schema 都不让看。后来才反应过来,schema 的 USAGE 没给。这个错不复杂,但挺典型:PostgreSQL 的权限不是给一张表就完事,它是分层管的,库、schema、对象各管各的,上面一层给了不代表下面自动通。

下面这些是我后来重新捋下来的,按自己的理解顺序写,不一定按官方文档目录来。

角色就是一切,所有者有"固有权利"

PostgreSQL 里没有单独的"用户"概念,只有角色。能登录的叫 LOGIN 角色,不能登录的也可以建,专门当权限容器用。建对象的人默认是对象的所有者。

这里有个容易绕的点:所有者对对象的权利是固有的,比如改表定义、DROP、GRANT/REVOKE,这些不是靠某条 GRANT 得来的。普通对象权限可以授来授去,但"所有权"本身不是普通权限,要换所有者得用 ALTER TABLE ... OWNER TO ... 这类语句。所有者的成员角色可以通过继承拿到这些权利,但概念上别把它和 GRANT SELECT 混在一起。

所以一个新角色刚建出来,哪怕能连库,默认也几乎什么都干不了。权限得一层一层给。

权限是分层的,不能越级

一个角色要读一张表,通常得同时具备:

  • 数据库上的 CONNECT,不然连不上;
  • schema 上的 USAGE,不然看不到里面的对象;
  • 表上的 SELECT,不然读不了。

少哪层报哪层的错。我那次就是库和表都给了,schema 的 USAGE 漏了。

给权限的顺序一般也是从外往里:

sql 复制代码
GRANT CONNECT ON DATABASE mydb TO readonly;
GRANT USAGE ON SCHEMA app TO readonly;
GRANT SELECT ON TABLE app.orders TO readonly;

ALL PRIVILEGES 也不是万能钥匙。它只覆盖该对象类型上可授予的那些权限,不包含数据库的 CONNECT,不包含 schema 的 USAGE,更不包含所有权带来的 DROP。所以看到 GRANT ALL 别默认万事大吉,还是得看给在哪一层。

GRANT 和 REVOKE 的两个坑

WITH GRANT OPTION 是让被授权者可以继续转授:

sql 复制代码
GRANT SELECT ON TABLE orders TO alice WITH GRANT OPTION;

alice 可以把 SELECT 再授给别人。后面如果撤销 alice 的权限,默认的 RESTRICT 会阻止操作,因为下游还有人依赖她的授权;用 CASCADE 会连锁撤销下去。这个在授权链长了以后要小心,别随手 CASCADE。

另一个是 GRANT OPTION FOR,只撤销"转授资格",保留权限本身:

sql 复制代码
REVOKE GRANT OPTION FOR SELECT ON TABLE orders FROM alice;

还有特殊角色名 PUBLIC,代表系统里每一个角色。GRANT ... TO PUBLIC 等于向所有人开放,生产环境里看到这种语句我一般会停下来多看两眼。

继承和 SET ROLE:自动拿权限,还是临时切换

角色成员关系有两种用法,取决于成员角色有没有 INHERIT。

默认是 INHERIT,成员自动拥有所属角色的权限:

sql 复制代码
CREATE ROLE app_readonly NOINHERIT;
CREATE ROLE alice LOGIN INHERIT;
GRANT app_readonly TO alice;

alice 登录后直接就有 app_readonly 的权限,不用切换。

如果是 NOINHERIT,成员不会自动获得组权限,得手动:

sql 复制代码
SET ROLE app_readonly;
-- 干只读的活
RESET ROLE;

这里注意,SET ROLE 是替换当前会话的权限集,不是叠加。切到 app_readonly 之后,alice 自己直接持有的权限在会话里暂时用不了,切回去才恢复。

另外,LOGIN、SUPERUSER、CREATEDB、CREATEROLE 这类角色属性不会通过继承自动生效。哪怕成员是 INHERIT,想用这些属性也得 SET ROLE 到持有属性的角色。

新表不会自动继承授权,默认权限得提前设

这是上线后最容易冒出来的问题:今天给现有表 GRANT 了,明天开发新建一张表,只读账号又报 42501。因为直接 GRANT 只影响当前已有的对象,未来表不认。

ALTER DEFAULT PRIVILEGES 就是干这个的:

sql 复制代码
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
    GRANT SELECT ON TABLES TO app_readonly;

ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
    GRANT USAGE, SELECT ON SEQUENCES TO app_readwrite;

它只影响未来创建的对象,不改已有对象;而且只能为当前角色或当前角色所属的角色设置。常见做法是由对象所有者角色来设,比如上面的 app_owner,这样谁建表都按这个规则走。

public schema 该动手就动手

PostgreSQL 15 之前,public schema 默认对 PUBLIC 给了 CREATE 和 USAGE,意思是任何能连库的角色都能在 public 里建表。PG15 起默认撤销了 CREATE,但很多库是从老版本升上来的,权限可能还在。所以不管新库老库,检查一下不亏:

sql 复制代码
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

需要更严的话,连 USAGE 也一起收,再给每个用户建同名私有 schema,调整 search_path,让未限定名称的对象落到自己 schema 里,而不是共享的 public。

RLS(行级安全):能读表,和能读哪些行,是两码事

表级 GRANT SELECT 管的是"能不能读这张表",行级安全管的是"读到哪些行"。RLS 默认关闭,得先开:

sql 复制代码
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;

然后建策略:

sql 复制代码
CREATE POLICY customer_own_rows ON customer
    FOR ALL
    TO app_user
    USING (owner_id = current_user_id())
    WITH CHECK (owner_id = current_user_id());

USING 管已有行对查询是否可见,也管 UPDATE/DELETE 的目标行;WITH CHECK 管新写入或修改后的行能不能落库。

一个容易忽略的点:表开了 RLS 但一条策略都没建,默认是拒绝所有行访问的。表所有者通常例外,除非上 FORCE ROW LEVEL SECURITY。多租户场景里经常配合 current_setting('app.tenant_id') 这类会话变量用,在同一套表里做行级隔离。

几个我见过的误区

GRANT ALL 不等于所有权,前面说过,它只覆盖可授予权限。

新表不会自动继承旧表授权,必须靠 ALTER DEFAULT PRIVILEGES 或建表后显式 GRANT。

超级用户(postgres)绕过所有权限检查,应用如果长期拿超级用户连库,上面这些配置基本等于白做。

我现在配权限的顺手流程

一般先建不带 LOGIN 的组角色,比如 app_readonly、app_readwrite,把对象权限授给角色,再把角色授给具体登录用户。权限调整时改角色成员就行,不用逐用户改。

然后按库、schema、对象逐层给,别跳。对象所有者角色设好 ALTER DEFAULT PRIVILEGES,避免新表上线漏权限。public schema 的 CREATE 收掉。应用连接用最小权限角色,不用超级用户。

排查的时候我常用这几个:

sql 复制代码
\dp app.orders
\ddp

\dp 看表、视图、序列的权限,\ddp 看默认权限。要精确查可以用:

sql 复制代码
SELECT * FROM information_schema.role_table_grants
WHERE table_schema = 'app' AND table_name = 'orders';

函数里判断权限用 has_table_privilege()、has_schema_privilege() 也方便。

差不多就这些。PostgreSQL 这套权限设计其实挺清楚,坑大多在"以为给了"和"其实没给"的边界上。先把分层和默认权限这两件事记住,能少排不少 42501。

相关推荐
呆萌很1 小时前
达梦(DM8)数据库常用SQL
数据库·sql
跟太阳说早安1 小时前
极简数据库SQL
数据库·sql
前端 贾公子1 小时前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
杨云龙UP2 小时前
MySQL 主从复制管理:暂停、恢复、清理主从关系及命令执行位置
运维·数据库·mysql·主从配置·主从命令·reset replica·清除复制
yzs872 小时前
openGauss branch diff能力解析
数据库·oracle
郭小李2 小时前
spring中@Transactional注解对事务的支持
数据库·spring·oracle
运维行者_2 小时前
供应链安全成最大风险源,企业如何构建可控的运维体系?
运维·开发语言·网络·数据库·安全·云计算·流量监控
高铭杰2 小时前
Postgresql源码(160)新的共享内存注册机制,为动态调整shared_buffers打基础
数据库·postgresql
数据库小学妹2 小时前
AI Agent连数据库怎么控权限?只读账号的4个缺口
数据库·人工智能·安全·数据库权限·审计·ai数据库·只读账号