内容提要
PostgreSQL 没有只读角色属性。文章在 17.11 上实测三种方案:仅授予 SELECT 权限最可控,但角色仍可调用 SECURITY DEFINER 函数改数据、建临时表和大型对象、修改自身密码与超时设置;pg_read_all_data 可读所有库表及 pg_authid 密码验证符和统计信息,范围过宽;default_transaction_read_only 只是会话可改的默认值,并非权限。行级安全对两者均有效,但属主视图仍可绕过。
延伸解读
三种“只读”方案的实际差异
文章在 PostgreSQL 17.11 上实测了三种常见做法:仅授予 SELECT 权限、加入 pg_read_all_data 预定义角色、设置 default_transaction_read_only。结果差异显著:SELECT 权限最可控,但角色仍能通过 SECURITY DEFINER 函数改数据;pg_read_all_data 可读所有库表及 pg_authid 中的密码验证符,范围过宽;default_transaction_read_only 只是会话可改的默认值,并非权限,会话可自行关闭。选择
只读角色仍可写数据的路径
即使只授予 CONNECT、USAGE 和 SELECT,角色仍能通过多种方式产生写效果:调用 SECURITY DEFINER 函数可更新行;创建临时表和大型对象会占用磁盘且大型对象持久保留;可修改自身密码和 statement_timeout 等角色级设置;还能获取咨询锁、发送 NOTIFY、执行 COPY TO 导出数据。这些路径说明“只读”不能仅靠表级权限实现,需额外撤销函数 EXECUTE、临时表权限和大型对象函数权限。
pg_read_all_data 的读取范围与风险
pg_read_all_data 成员可读取所有 schema 中的表、视图和序列,包括系统目录。实测中,成员能读取 pg_authid 中的密码验证符(SCRAM-SHA-256 格式),这是离线猜测攻击所需材料;还能从 pg_statistic 读取被行级安全隐藏的表的采样值,可能泄露客户数据。该角色适用于需要读取所有 schema 的作业,但对面向人员或 BI 工具的报表登录,其读取范围远超应用数据,需谨慎评估。
行级安全与 pg_dump 的交互
行级安全对 SELECT 授权角色和 pg_read_all_data 成员均有效,两者在无策略时都返回 0 行。但属主视图可绕过策略,除非启用 FORCE ROW LEVEL SECURITY 或使用 security_invoker。pg_dump 默认关闭 row_security,遇到受策略保护的表会报错;使用 --enable-row-security 可完成转储但会过滤行。备份角色若需完整转储,需 BYPASSRLS 或针对该角色的策略配合 --enable-row-security。
Q&A
PostgreSQL 里怎么创建一个只读用户?
创建登录角色,授予数据库 CONNECT、模式 USAGE、现有表的 SELECT 和序列的 SELECT,并为将来创建的表设置默认权限。例如:create role reporting login password '…'; grant connect on database shop to reporting; grant usage on schema app to reporting; grant select on all tables in schema app to reporting; grant select on all sequences in schema app to reporting; alter default privileges for role migrator in schema app grant select on tables to reporting; 其中 migrator 是创建表的角色。
设置 default_transaction_read_only = on 能让用户变成只读吗?
不能。default_transaction_read_only 只是一个会话可以更改的默认值,不是权限。拥有写权限的角色可以执行 set default_transaction_read_only = off 或 begin read write 来绕过它。它只能防止意外写入,不能真正限制角色。
只读用户还能做哪些写操作?
只读用户仍可调用 SECURITY DEFINER 函数修改数据、创建临时表和大型对象、修改自身密码和超时设置、设置 work_mem、获取咨询锁、发送通知、锁定表、复制数据到 stdout、查看函数源码、统计角色和数据库数量、连接其他数据库并创建临时表。
pg_read_all_data 角色能读取哪些数据?
pg_read_all_data 成员可以读取所有数据库、所有模式中的表、视图和序列,包括系统目录。实测中,成员能读取未授予权限的模式中的表、之后创建的表,以及 pg_authid 中的密码验证符(如 SCRAM-SHA-256 哈希)。还能从 pg_statistic 读取行级安全策略隐藏的表的统计信息。
只读用户能绕过行级安全(RLS)吗?
不能直接绕过。通过表访问时,只读用户和 pg_read_all_data 成员都受 RLS 限制,返回 0 行。但如果存在属主视图(视图属主是表属主),只读用户可以通过该视图读取所有租户的数据。启用 FORCE ROW LEVEL SECURITY 或设置 security_invoker = true 可以阻止。
只读用户能运行 pg_dump 吗?
仅授予表 SELECT 权限时不能完整转储,因为 pg_dump 需要读取序列,且遇到行级安全会报错。需要额外授予序列的 SELECT,并处理 RLS:要么给角色 BYPASSRLS,要么使用 --enable-row-security 并配合针对该角色的 using (true) 策略。