Skip to content

权限系统速查 ​

这一页把「安全与权限」整节的干货压缩成可直接复制使用的清单:权限矩阵、五个高频场景的标准脚本、权限检查函数、目录查询、psql 元命令、错误码与预设角色。建议把它当作工作台旁的参考卡,需要展开原理时再回到前面的分页。

前置知识

建议先阅读:默认权限(ALTER DEFAULT PRIVILEGES)

基础概念 ​

回顾一下贯穿全节的判定链——任何一次「连不上 / 读不了」的排查都沿它进行:

text
pg_hba.conf 放行
  → 数据库 CONNECT
    → 模式 USAGE
      → 对象权限(表/序列/函数…,含列级)
        ← 四个权限来源的并集:属主 ∪ 直接授权 ∪ 成员继承(INHERIT) ∪ PUBLIC

使用本页的三种姿势:配环境直接抄「常用场景速查」;查现状用「常用查询速查」与 psql 元命令;排错先看错误码速查再回到判定链找断点。

权限矩阵速查 ​

对象 × 可授予权限 × 默认 PUBLIC ​

对象类型可授予权限出厂授予 PUBLIC
数据库CONNECT、CREATE、TEMPORARY(别名 TEMP)CONNECT、TEMPORARY
模式USAGE、CREATE无
表 / 视图 / 外表SELECT、INSERT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER、MAINTAIN无
列SELECT、INSERT、UPDATE、REFERENCES无
序列USAGE、SELECT、UPDATE无
函数 / 聚合 / 过程EXECUTEEXECUTE
语言USAGEUSAGE
大对象SELECT、UPDATE无
类型 / 域USAGE无
外部数据封装器(FDW)USAGE无
外部服务器USAGE无
表空间CREATE无
配置参数(PG15+)SET、ALTER SYSTEM无

另有两条「不是权限的权力」:删除/修改对象的定义内生于属主身份,不可 GRANT;角色成员关系不能授予 PUBLIC。

ACL 缩写对照(\dp 输出用) ​

缩写权限缩写权限
rSELECTXEXECUTE
aINSERTUUSAGE
wUPDATECCREATE
dDELETEcCONNECT
DTRUNCATETTEMPORARY
xREFERENCESmMAINTAIN(PG17+)
tTRIGGER*前一个权限带 GRANT OPTION

\dp 输出格式为 grantee=privs/grantor,grantee 为空表示 PUBLIC。

常用场景速查 ​

只读用户 ​

sql
CREATE ROLE ro_user LOGIN PASSWORD 'R0_P@ss' CONNECTION LIMIT 10;

GRANT CONNECT ON DATABASE appdb TO ro_user;          -- hba 也要放行
GRANT USAGE ON SCHEMA app TO ro_user;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO ro_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA app TO ro_user;   -- 需要currval/lastval时

-- 未来新表自动可读(deploy 为实际建表角色)
ALTER DEFAULT PRIVILEGES FOR ROLE deploy IN SCHEMA app
    GRANT SELECT ON TABLES TO ro_user;

读写用户 ​

sql
CREATE ROLE rw_user LOGIN PASSWORD 'Rw_P@ss' CONNECTION LIMIT 30;

GRANT CONNECT ON DATABASE appdb TO rw_user;
GRANT USAGE ON SCHEMA app TO rw_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO rw_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO rw_user;   -- 自增列必需

ALTER DEFAULT PRIVILEGES FOR ROLE deploy IN SCHEMA app
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO rw_user;
ALTER DEFAULT PRIVILEGES FOR ROLE deploy IN SCHEMA app
    GRANT USAGE, SELECT ON SEQUENCES TO rw_user;

DDL 管理员 ​

DDL 需要属主身份,推荐「成员 + SET ROLE」而不是直接给权限:

sql
-- app_owner 为 NOLOGIN 属主组
CREATE ROLE dba_dev LOGIN PASSWORD 'Dba_P@ss' NOINHERIT;
GRANT app_owner TO dba_dev WITH INHERIT TRUE, SET TRUE;

GRANT USAGE, CREATE ON SCHEMA app TO dba_dev;   -- 模式内建对象
GRANT pg_maintain TO dba_dev;                   -- 跨对象维护(VACUUM/REINDEX 等,PG17+)

-- 发布流程约定:先切换属主再跑 DDL,对象归组不归人
-- SET ROLE app_owner; CREATE TABLE ...; RESET ROLE;

监控用户 ​

sql
CREATE ROLE monitoring LOGIN PASSWORD 'Mon_P@ss' CONNECTION LIMIT 5;
GRANT pg_monitor TO monitoring;                  -- 读配置 + pg_stat_* + 扫描类函数

-- 自定义 SQL 需要读业务数据时(可选,注意不绕过 RLS)
GRANT pg_read_all_data TO monitoring;

备份用户 ​

sql
-- 逻辑备份(pg_dump / pg_dumpall):读所有数据即可
CREATE ROLE backup_logical LOGIN PASSWORD 'Bak_P@ss';
GRANT pg_read_all_data TO backup_logical;        -- 含所有模式 USAGE + 全表 SELECT
GRANT CONNECT ON DATABASE appdb TO backup_logical;
-- 库若已 REVOKE CONNECT FROM PUBLIC,上面这句不可省

-- 物理备份(pg_basebackup):需要复制属性,且 pg_hba.conf 放行 replication
CREATE ROLE backup_physical LOGIN REPLICATION PASSWORD 'R3pl_P@ss';

备份与 RLS

pg_dump 默认把 row_security 设为 off,若备份角色不能绕过 RLS 且表启用了 RLS,导出会报错。处理方式:让备份角色成为属主角色的成员,或显式给备份角色 BYPASSRLS。

ETL / 批处理写入用户 ​

批处理账号的原则是读写范围最小化 + 可观测:只对它该碰的 staging 模式有写权,对业务模式至多只读;statement_timeout 防失控。

sql
CREATE ROLE etl_batch LOGIN PASSWORD 'Etl_P@ss' CONNECTION LIMIT 5;

-- 目标:只写 staging 模式
GRANT CONNECT ON DATABASE appdb TO etl_batch;
GRANT USAGE, CREATE ON SCHEMA staging TO etl_batch;   -- 建中间表
GRANT SELECT ON ALL TABLES IN SCHEMA app TO etl_batch; -- 读源(按需缩到具体表)

-- 未来 staging 新表自动可写
ALTER DEFAULT PRIVILEGES FOR ROLE etl_batch IN SCHEMA staging
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO etl_batch;
ALTER DEFAULT PRIVILEGES FOR ROLE etl_batch IN SCHEMA staging
    GRANT USAGE, SELECT ON SEQUENCES TO etl_batch;

-- 护栏:长任务超时 + 失败可重试
ALTER ROLE etl_batch SET statement_timeout = '4h';
ALTER ROLE etl_batch SET idle_in_transaction_session_timeout = '10min';

迁移 / 发布流水线账号 ​

发布账号是「会建对象」的角色,必须进入默认权限的登记矩阵(见上一页),并与 app_owner 属主模式配合:

sql
CREATE ROLE flyway LOGIN PASSWORD 'Fly_P@ss' CONNECTION LIMIT 3;

GRANT CONNECT ON DATABASE appdb TO flyway;
GRANT USAGE, CREATE ON SCHEMA app TO flyway;      -- 建表 / 建索引
GRANT pg_maintain TO flyway;                      -- 维护类操作(PG17+,如 REINDEX)

-- 发布账号是 app_owner 的成员:DDL 统一以属主身份执行,对象归组
GRANT app_owner TO flyway WITH INHERIT FALSE, SET TRUE;

-- 关键:flyway 建的新表自动带上业务组授权(见默认权限页的登记矩阵)
ALTER DEFAULT PRIVILEGES FOR ROLE flyway IN SCHEMA app
    GRANT SELECT ON TABLES TO app_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE flyway IN SCHEMA app
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE flyway IN SCHEMA app
    GRANT USAGE, SELECT ON SEQUENCES TO app_readwrite;

注意 INHERIT FALSE + SET TRUE 的组合:发布工具平时无 DDL 权限,迁移脚本开头显式 SET ROLE app_owner; 才获得属主能力——迁移日志因此天然记录了每次「变身」。

逻辑复制账号(发布端 / 订阅端) ​

逻辑复制两端各需要一个专用角色,能力不对称:

sql
-- ── 发布端:复制身份 + 全部被发布表的读权 ──────────────
CREATE ROLE repl_logical LOGIN REPLICATION PASSWORD 'R3plL0g_P@ss';
GRANT USAGE ON SCHEMA app TO repl_logical;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO repl_logical;
-- pg_hba.conf 需为该角色放行(database/replication 条目)
ALTER DEFAULT PRIVILEGES FOR ROLE deploy IN SCHEMA app
    GRANT SELECT ON TABLES TO repl_logical;      -- 新表别漏

-- ── 订阅端:创建订阅的角色需要 pg_create_subscription(PG16+)
--    且对所在库有 CREATE 权限 ──────────────────────────
GRANT pg_create_subscription TO ops_dba;

两个易漏点:发布端角色必须带 REPLICATION 属性,且 pg_hba 放行类型为 replication 的连接;FOR ALL TABLES 的发布(publication)不会自动包含未来新建的表授权——上面那条默认权限模板正是为「新表别漏读权」准备的。

权限检查函数速查 ​

全部位于 pg_catalog,user 参数可省(省略时检查 current_user),也可传 'public' 检查 PUBLIC;对象可传名字(可含模式)或 OID;权限串不区分大小写,可用逗号列出多个(任一成立即真),可加 WITH GRANT OPTION。

函数可用权限类型
has_table_privilege([user,] table, privilege)SELECT、INSERT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER、MAINTAIN
has_column_privilege([user,] table, column, privilege)SELECT、INSERT、UPDATE、REFERENCES
has_any_column_privilege([user,] table, privilege)SELECT、INSERT、UPDATE、REFERENCES
has_sequence_privilege([user,] sequence, privilege)USAGE、SELECT、UPDATE
has_schema_privilege([user,] schema, privilege)CREATE、USAGE
has_database_privilege([user,] database, privilege)CREATE、CONNECT、TEMPORARY(TEMP)
has_function_privilege([user,] function, privilege)EXECUTE
has_type_privilege([user,] type, privilege)USAGE
has_language_privilege([user,] language, privilege)USAGE
has_largeobject_privilege([user,] loid, privilege)SELECT、UPDATE
has_tablespace_privilege([user,] tablespace, privilege)CREATE
has_foreign_data_wrapper_privilege([user,] fdw, privilege)USAGE
has_server_privilege([user,] server, privilege)USAGE
has_parameter_privilege([user,] parameter, privilege)SET、ALTER SYSTEM
pg_has_role([user,] role, privilege)MEMBER、USAGE、SET
sql
SELECT has_table_privilege('app_ro', 'app.orders', 'SELECT');
SELECT has_table_privilege('joe', 'mytable', 'INSERT, SELECT WITH GRANT OPTION');
SELECT has_function_privilege('app_rw', 'public.myfunc(int, text)', 'execute');
SELECT has_column_privilege('app_ro', 'app.orders', 'amount', 'SELECT');
SELECT pg_has_role('alice', 'app_readwrite', 'MEMBER');  -- MEMBER/USAGE/SET 三态

pg_has_role 的三态语义:MEMBER = 直接或间接成员;USAGE = 权限无需 SET ROLE 即可用(继承到位);SET = 可以 SET ROLE 切换过去。

判断超级用户:is_superuser 是会话状态(报告参数),不是 SQL 函数:

sql
SHOW is_superuser;                                  -- on / off
SELECT current_setting('is_superuser');
SELECT rolsuper FROM pg_roles WHERE rolname = current_user;

常用查询速查 ​

查角色 ​

sql
-- 登录角色及关键属性
SELECT rolname, rolcanlogin, rolconnlimit, rolinherit,
       rolsuper, rolcreatedb, rolcreaterole, rolreplication, rolbypassrls,
       rolvaliduntil
FROM pg_roles
ORDER BY rolcanlogin DESC, rolname;

-- 角色级参数(ALTER ROLE ... SET 的结果)
SELECT rolname, rolconfig FROM pg_roles WHERE rolconfig IS NOT NULL;

查对象权限 ​

sql
-- 某模式内全部表/视图的 ACL(配合 \dp 使用)
SELECT c.relname, c.relkind, c.relacl
FROM pg_class c
WHERE c.relnamespace = 'app'::regnamespace
  AND c.relkind IN ('r', 'v', 'S')
ORDER BY c.relname;

-- 解析成一行一权限的可读形式(grantee = 0 即 PUBLIC)
SELECT c.relname,
       COALESCE(pg_get_userbyid(a.grantee), 'PUBLIC') AS grantee,
       a.privilege_type, a.is_grantable
FROM pg_class c,
     LATERAL aclexplode(c.relacl) a
WHERE c.relnamespace = 'app'::regnamespace
ORDER BY c.relname, 2;

-- information_schema 视角(表级授权,仅自己经手/持有的部分)
SELECT grantor, grantee, table_schema, table_name, privilege_type, is_grantable
FROM information_schema.role_table_grants
WHERE table_schema = 'app';

查成员关系 ​

sql
-- 直接成员
SELECT g.rolname AS group_role, m.rolname AS member,
       am.admin_option, am.inherit_option, am.set_option   -- 后两列 PG16+
FROM pg_auth_members am
JOIN pg_roles g ON g.oid = am.roleid
JOIN pg_roles m ON m.oid = am.member
ORDER BY 1, 2;

-- 递归:某角色的全部间接组(向上)
WITH RECURSIVE up AS (
    SELECT am.roleid, 1 AS depth
    FROM pg_auth_members am WHERE am.member = 'alice'::regrole
  UNION ALL
    SELECT am.roleid, u.depth + 1
    FROM pg_auth_members am JOIN up u ON am.member = u.roleid
)
SELECT r.rolname, up.depth FROM up
JOIN pg_roles r ON r.oid = up.roleid;

查默认权限 ​

sql
SELECT r.rolname AS creating_role, n.nspname AS schema,
       d.defaclobjtype,    -- r=表 S=序列 f=函数 T=类型 n=模式
       d.defaclacl
FROM pg_default_acl d
LEFT JOIN pg_roles r      ON r.oid = d.defaclrole
LEFT JOIN pg_namespace n  ON n.oid = d.defaclnamespace;

查对象属主 ​

sql
-- 表/视图/序列/函数的属主
SELECT c.relname, pg_get_userbyid(c.relowner) AS owner
FROM pg_class c WHERE c.relnamespace = 'app'::regnamespace;

SELECT p.proname, pg_get_userbyid(p.proowner) AS owner
FROM pg_proc p WHERE p.pronamespace = 'app'::regnamespace;

-- 模式与数据库的属主
SELECT n.nspname, pg_get_userbyid(n.nspowner) FROM pg_namespace n;
SELECT datname, pg_get_userbyid(datdba) FROM pg_database;

查函数与参数权限 ​

sql
-- 函数 ACL(proacl 为 NULL 表示「走出厂默认」:属主 + PUBLIC EXECUTE)
SELECT p.proname, p.proacl
FROM pg_proc p
WHERE p.pronamespace = 'app'::regnamespace
ORDER BY 1;

-- 找出仍在出厂状态(PUBLIC 可执行)的业务函数
SELECT p.proname
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND (p.proacl IS NULL
       OR EXISTS (SELECT 1 FROM aclexplode(p.proacl) a
                  WHERE a.grantee = 0 AND a.privilege_type = 'EXECUTE'));

-- 配置参数权限(PG15+:GRANT SET / ALTER SYSTEM ON PARAMETER)
SELECT has_parameter_privilege('ops_dba', 'work_mem', 'SET');

查会话与身份 ​

sql
-- 本会话身份三件套
SELECT session_user, current_user, system_user;

-- 全实例:谁连着、以什么身份、从哪来(usename 是登录角色)
SELECT pid, datname, usename, application_name,
       client_addr, backend_start, state
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;

psql 元命令速查 ​

命令用途
\du [pattern]列出角色,含「属于哪些组的成员 / 拥有哪些组的成员」
\du+ [pattern]同上,并显示角色级参数等描述信息
\dg [pattern]\du 的别名
\dp [pattern](= \z)列出表/视图/序列的访问权限(ACL)
\ddp [pattern]列出已登记的默认权限
\dn+ [pattern]列出模式,+ 含权限与说明
\l+ [pattern]数据库列表,+ 含 ACL 与大小
\drds [role [db]]按「角色 × 数据库」列出固化的会话默认参数
\dew+ [pattern]外部数据封装器(FDW)列表
\des+ [pattern]外部服务器列表
\dl大对象列表(\lo_list 的别名)

\du 与 \dp 的典型输出形态(权限解读依赖本页的 ACL 缩写表):

text
=> \du app_*
                                    List of roles
 Role name  |                 Attributes                  | Member of
------------+---------------------------------------------+---------------------------
 app_admin  | Cannot login                                | pg_maintain
 app_owner  | Cannot login                                | {}
 app_conn   | 30 connections                              | app_readonly, app_readwrite

=> \dp app.orders
                                      Access privileges
 Schema | Table  |             Access privileges             | Column privileges | Policies
--------+--------+-------------------------------------------+-------------------+----------
 app    | orders | app_owner=arwdDxtm/app_owner             +|                   |
        |        | app_readwrite=arwd/app_owner             +|                   |
        |        | app_readonly=r/app_owner                  |                   |

Member of 列即「该角色是哪些组的成员」,是 \du 比递归查询快得多的第一手审计视图;arwdDxtm 按缩写表逐字解码即可。

错误码速查 ​

SQLSTATE名称典型消息
42501insufficient_privilegepermission denied for table orders、permission denied for schema app、must be owner of table orders、permission denied to create database
28P01invalid_passwordpassword authentication failed for user "app_user"
28000invalid_authorization_specificationrole "app_user" does not exist、no pg_hba.conf entry for host ...

排错对照:28P01/28000 发生在连接建立阶段(先查 pg_hba 与角色是否存在);42501 发生在语句执行阶段(按判定链查 CONNECT → USAGE → 对象权限 → 列级)。

42501 之下的细分定位表:

报错消息形态断点快速确认
no pg_hba.conf entry ...(实为 28000)hba 未放行来源检查 pg_hba.conf 与 pg_reload_conf()
password authentication failed(28P01)密码错 / 密码过期rolvaliduntil、密码加密方式
role "x" does not exist角色不存在或 hba 匹配错误\du x
permission denied for database appdb缺 CONNECThas_database_privilege(..., 'CONNECT')
permission denied for schema app缺 USAGEhas_schema_privilege(..., 'USAGE')
permission denied for table orders缺对象权限(或 NOINHERIT 未切换)三态 pg_has_role() + has_table_privilege
permission denied for sequence orders_id_seq表给了、序列没给has_sequence_privilege
permission denied for function fEXECUTE 被 PUBLIC 回收后未单独授予has_function_privilege
must be owner of table ordersDDL 要求属主而非普通权限pg_get_userbyid(relowner)
permission denied to set role to "x"成员关系 SET FALSE 或非成员pg_has_role(..., 'SET')

预设角色速查 ​

预设角色能力版本
pg_signal_backend取消/终止其他普通会话(不含超级用户)9.6
pg_monitor监控合集 = 下面三个角色的成员10
pg_read_all_settings读全部配置参数10
pg_read_all_stats读全部 pg_stat_* 统计视图10
pg_stat_scan_tables执行可能长时间持 ACCESS SHARE 锁的监控函数10
pg_read_server_files读服务器文件(高危)11
pg_write_server_files写服务器文件(高危)11
pg_execute_server_program执行服务器端程序(高危)11
pg_database_owner隐式成员为当前库属主;初始拥有 public 模式14
pg_read_all_data读所有表/视图/序列 + 所有模式 USAGE(不绕过 RLS)14
pg_write_all_data写所有表/视图/序列 + 所有模式 USAGE(不绕过 RLS)14
pg_checkpoint执行 CHECKPOINT14
pg_create_subscription建立逻辑订阅(还需库上 CREATE)16
pg_maintain对所有关系执行 VACUUM/ANALYZE/CLUSTER/REFRESH MV/REINDEX/LOCK TABLE17
pg_use_reserved_connections使用 reserved_connections 保留连接槽17
pg_signal_autovacuum_worker取消/终止 autovacuum worker18

实战示例 ​

示例:一键权限体检脚本 ​

只读检查,直接在目标库上运行;输出任何一节非空都值得人工复核:

bash
#!/usr/bin/env bash
# usage: ./perm-check.sh [dbname]
psql -X -U postgres -d "${1:-appdb}" -v ON_ERROR_STOP=1 <<'SQL'
\pset pager off
\set QUIET off

\echo '=== 1. postgres 之外的超级用户 ==='
SELECT rolname FROM pg_roles WHERE rolsuper AND rolname <> 'postgres';

\echo '=== 2. 登录角色中的高危属性持有者 ==='
SELECT rolname, rolcreatedb, rolcreaterole, rolreplication, rolbypassrls
FROM pg_roles
WHERE rolcanlogin AND (rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls);

\echo '=== 3. 密码永不过期的登录角色 ==='
SELECT rolname FROM pg_roles WHERE rolcanlogin AND rolvaliduntil IS NULL;

\echo '=== 4. 连接数不受限的登录角色 ==='
SELECT rolname FROM pg_roles WHERE rolcanlogin AND rolconnlimit = -1;

\echo '=== 5. 当前库的 PUBLIC 授权状态(datacl 应无 PUBLIC=CTc/...) ==='
SELECT datname, datacl FROM pg_database WHERE datname = current_database();

\echo '=== 6. public 模式是否仍对 PUBLIC 开放 CREATE ==='
SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public';

\echo '=== 7. 已登记的默认权限模板 ==='
SELECT r.rolname AS creating_role, n.nspname AS schema, d.defaclobjtype, d.defaclacl
FROM pg_default_acl d
LEFT JOIN pg_roles r ON r.oid = d.defaclrole
LEFT JOIN pg_namespace n ON n.oid = d.defaclnamespace;

\echo '=== 8. ADMIN 选项持有者(转授权风险点) ==='
SELECT g.rolname AS group_role, m.rolname AS member
FROM pg_auth_members am
JOIN pg_roles g ON g.oid = am.roleid
JOIN pg_roles m ON m.oid = am.member
WHERE am.admin_option;

\echo '=== 9. 业务模式中仍默认对 PUBLIC 可执行的函数数量 ==='
SELECT count(*) AS public_executable_functions
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND (p.proacl IS NULL
       OR EXISTS (SELECT 1 FROM aclexplode(p.proacl)
                  WHERE a.grantee = 0 AND a.privilege_type = 'EXECUTE'));

\echo '=== 10. 持有组成员身份的「组」(LOGIN 组形态检查) ==='
SELECT g.rolname AS group_role, count(*) AS members, g.rolcanlogin
FROM pg_auth_members am
JOIN pg_roles g ON g.oid = am.roleid
GROUP BY g.rolname, g.rolcanlogin
HAVING g.rolcanlogin;    -- 能登录又挂着成员的组,形态异常
SQL

把脚本接入巡检(crontab / CI),第 1、2、8、10 节应为空,第 5、6 节的 ACL 应与基线一致。

示例二:CI 权限断言(对象权限版) ​

《角色继承》页的断言脚本管成员关系三态,这里补上对象权限侧的断言矩阵。把期望值写成 SQL 数组,脚本逐条比对,任何漂移让流水线变红:

bash
#!/usr/bin/env bash
# usage: ./assert-grants.sh <connstr>
set -euo pipefail
CONN="${1:?usage: assert-grants.sh <connstr>}"

assert_eq() {  # $1=描述 $2=期望 $3=实际SQL
  got=$(psql "$CONN" -XAt -v ON_ERROR_STOP=1 -c "$3")
  if [ "$got" = "$2" ]; then echo "OK   $1"; else echo "FAIL $1 (期望 $2,实际 $got)"; exit 1; fi
}

# 只读组:能读、不能写
assert_eq "ro SELECT orders"  "t" "SELECT has_table_privilege('app_readonly','app.orders','SELECT')"
assert_eq "ro INSERT orders"  "f" "SELECT has_table_privilege('app_readonly','app.orders','INSERT')"

# 读写组:DML 全开、序列可用、不能 TRUNCATE
for p in SELECT INSERT UPDATE DELETE; do
  assert_eq "rw $p orders" "t" "SELECT has_table_privilege('app_readwrite','app.orders','$p')"
done
assert_eq "rw TRUNCATE orders" "f" "SELECT has_table_privilege('app_readwrite','app.orders','TRUNCATE')"
assert_eq "rw seq USAGE"       "t" "SELECT has_sequence_privilege('app_readwrite','app.orders_id_seq','USAGE')"

# 模式与函数基线
assert_eq "rw schema USAGE"        "t" "SELECT has_schema_privilege('app_readwrite','app','USAGE')"
assert_eq "PUBLIC 无函数执行权"     "f" "SELECT has_function_privilege('public','app.echo_hi()','EXECUTE')"

echo "all grants match"

断言矩阵与建库脚手架放在同一仓库:脚手架改了授权,CI 断言同步改,评审时两处 diff 互相印证——这是防止「环境间权限漂移」成本最低的机制。

注意事项 ​

速查表代替不了判定链。 遇到 42501 先走「hba → CONNECT → USAGE → 对象 → 列级」的顺序定位,再回到对应分页找语法细节;速查页只负责「查得快」。

\dp / \ddp 的输出是缩写。 读不懂 arwdDxt 时回到 ACL 缩写对照表;grantee=privs/grantor 中 grantor 也值得看——它暴露了是谁授的权。

information_schema 视图是不完整的。 role_table_grants 只显示与当前用户相关的授权,完整视图永远是 pg_catalog 的 ACL 列(relacl、nspacl、datacl 等)加 aclexplode。

权限检查函数按「某角色 + 某对象」提问。 它们回答的是布尔问题,不解释「权限从哪来」;来源分析要查 pg_auth_members 与 pg_default_acl。

预设角色列表随版本变化。 官方明确提示预设角色的具体权限未来可能调整,跨版本升级时以对应版本的文档为准,不要把本页表格当作跨版本契约。

体检脚本以只读查询为主。 所有查询只读 pg_* 目录,但脚本本身要放在受控位置并限制执行者——它输出的角色清单本身就是敏感信息。

has_*_privilege 对超级用户恒真。 用它断言「应有权限」没问题,但断言「应无权限」时若目标角色碰巧是超级用户,结果永远是 t,会让反向断言失效——反向断言前先确认目标不是超级用户。

预设角色是「最小可用集合」不是「最佳实践配置」。 例如 pg_read_all_data 覆盖全库所有表,调试时方便、常态启用则过宽;速查表给的是「能力边界」,选型仍要回到各场景的脚本。

最佳实践 ​

  1. 把本页「常用场景速查」固化为团队脚手架(建库脚本模板),五个场景的标准脚本与基线 REVOKE(CONNECT/public 模式 CREATE/函数 EXECUTE)一起进版本库。
  2. 每个新环境上线前跑一遍权限体检脚本,并把输出存档作为基线;巡检时对比两次输出的 diff。
  3. CI 中用权限检查函数做断言:对关键路径断言 has_table_privilege() / pg_has_role() 的期望值,环境漂移在流水线里拦截。
  4. \du 与 \dp 的输出纳入变更评审:成员关系与对象 ACL 的变更截图(或导出)随工单归档。
  5. 监控、备份、逻辑复制各用专用角色 + 预设角色,并在体检脚本中为它们建立白名单,防止「顺手又建了个超级用户」。
  6. 错误码写进运维手册的排错树:28P01/28000 找认证,42501 找权限链,再细分到具体对象类型。
  7. 高危预设角色(读/写服务器文件、执行服务器程序)设为「默认拒绝」:任何授予都要走专项审批并在体检脚本中单独列出。
  8. 多版本环境维护速查表的版本列:pg_auth_members 新列、MAINTAIN 权限、pg_maintain 等都有明确的版本边界,脚本里做好版本分支。
  9. ACL 变更走迁移脚本而不是手工 \dp 后抄 SQL:与 DDL 一样进 Flyway/Liquibase 流水线,保证环境可重建。
  10. 定期(季度)全量导出权限快照:角色、成员关系、对象 ACL、默认权限四张表导出存档,事故复盘与合规审计都依赖它。

下一节

继续学习:pg_hba.conf 客户端认证

参考链接 ​