set_user

1. 概述

set_user 是 PostgreSQL 安全审计扩展,由 pgaudit 项目维护,优化原生用户切换能力。支持普通用户间互切,也可受控切换至超级用户;可配置白名单限定允许切换的用户,所有切换操作留存审计日志,切换到超级用户时强制完整记录 SQL,拦截修改数据库配置、调用系统命令等高风险操作,规范临时提权,部署需预加载插件并重启数据库,再创建扩展使用。

版本:REL4_2_0

开源协议:PostgreSQL License

2. 原理介绍

PostgreSQL原生的 SET ROLE / SET SESSION AUTHORIZATION 有两个安全短板:一是提权后可以随手 SET log_statement = 'none' 关掉日志、RESET ROLE 悄悄切回来,审计留不下痕迹;二是要让 DBA 能干超级用户的活,通常得给他们能直接登录 superuser 账号,权限过大且不可控。

set_user 的思路是"不禁止提权,但让提权全程留痕、无法抵赖"。部署后,所有超级用户账号可设为 NOLOGIN,DBA 以普通账号登录,需要时调用 set_user_u('postgres') 提权。从提权到 reset_user() 恢复的整个窗口内:用户切换被记入日志,log_statement 被强制改为 all 使每条 SQL 落盘,日志前缀自动追加 AUDIT 标签便于过滤告警;同时 ALTER SYSTEM、COPY PROGRAM、SET log_statement、SET ROLE 及 set_config() 后门等所有可能破坏审计或逃逸身份的通道被全部封锁。由于 session_user 始终保持真实登录者,"谁在什么时候以什么身份做了什么"在日志中一目了然。

实现上,它以 C 扩展形式通过 shared_preload_libraries 加载,核心依赖三个内核机制:ProcessUtility_hook 拦截危险语句,object_access_hook 封堵函数级后门,事务提交回调保证切换的事务安全。权限控制采用双闸设计——SQL 层的 GRANT EXECUTE 决定谁能调用,配置层的白名单(superuser_allowlist 等)可随时热调整收口。此外还提供带口令锁的 set_user(user, token) 供连接池代持连接时防逃逸,以及不可逆的 set_session_auth() 用于连接移交前的永久降权。

set_user 并不能阻止超级用户作恶——它的定位是确保任何特权操作都无法逃避审计,配合日志告警系统,构成 PostgreSQL 最小权限运维体系的关键一环。

3. 使用说明

需要将 set_user 添加到 shared_preload_libraries。如果有其他插件实现 post-execution hooks 那么需要将 set_user 列在这些插件之前。
superuser 账号(如 postgres)需要设为 NOLOGIN,DBA 用普通账号登录,需要时调用 set_user_u('postgres') 提权。
  • 配置介绍

配置项 说明 示例 功能

set_user.block_alter_system

on (默认)或者 off

on

阻止 ALTER SYSTEM 命令

set_user.block_copy_program

on (默认)或者 off

on

阻止 COPY PROGRAM 命令

set_user.block_log_statement

on (默认)或者 off

on

阻止修改 log_statement

set_user.nosuperuser_target_allowlist

通配符 '*' (默认)或字符串

'dba1, dba2, +admin_group'

允许 set_user() 切换到的目标用户名单

set_user.superuser_allowlist

通配符 '*' (默认)或字符串

'dba1, dba2, +admin_group'

允许调用 set_user_u() 提权的用户名单

set_user.superuser_audit_tag

字符串

'AUDIT'

日志前缀标签

set_user.exit_on_error

on (默认)或者 off

on

出现错误时是否退出当前会话

  • 函数介绍

函数 默认权限 参数 说明

set_user(text)

REMOVE FROM PUBLIC

目标非superuser用户名

将当前会话的用户身份切换为指定的非superuser用户

set_user(text, text)

REMOVE FROM PUBLIC

目标非superuser用户名,随机token

同上,但需要提供一个随机token以增加安全性

set_user_u(text)

REMOVE FROM PUBLIC

目标superuser用户名

将当前会话的用户身份切换为指定的superuser用户

reset_user()

GRANT TO PUBLIC

将当前会话的用户身份恢复为原始用户

reset_user(text)

GRANT TO PUBLIC

随机token

带口令恢复,但是需要提供对应的token

set_session_auth(text)

REMOVE FROM PUBLIC

目标非superuser用户名

类似 SET SESSION AUTHORIZATION 但是只能切换到普通用户,并且不能切换回来

4. 安装

源码测试安装环境为 Ubuntu 26.04。需要安装IvorySQL,具体安装步骤请参考:https://github.com/IvorySQL/IvorySQL

4.1. 源码安装

  • 设置IvorySQL安装路径

export IVY_BIN_DIR=/usr/local/ivorysql/bin
export TEST_DB_DIR=/tmp/test_db
  • 获取源码

git clone https://github.com/pgaudit/set_user.git
cd set_user
git checkout REL4_2_0
  • 编译安装

make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config clean
make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config
make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config install
  • 配置

shared_preload_libraries = 'set_user'

4.2. 创建测试数据库

需要将 set_user 添加到 shared_preload_libraries
  • 初始化数据库

# 初始化数据库
${IVY_BIN_DIR}/initdb -U postgres -D ${TEST_DB_DIR}

# 将 set_user 添加到 shared_preload_libraries 中,也可以手动配置
sed -i "s/\(shared_preload_libraries = '.*\)'/\1, set_user'/g" ${TEST_DB_DIR}/ivorysql.conf

# 修改PG端口和Oracle兼容端口,避免冲突
cat << EOF >> ${TEST_DB_DIR}/ivorysql.conf
ivorysql.port = 1522
port = 5433
EOF

# 启动测试数据库
${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile start

4.3. 使用测试

  • 连接数据库

PGHOST=127.0.0.1 PGPORT=1522 PGUSER=postgres $IVY_BIN_DIR/psql
  • 测试示例

这里使用默认配置,即白名单为 '*。
CREATE EXTENSION set_user;
LOAD 'set_user';

--- 创建两个用户
CREATE USER dba;
CREATE USER bob;

-- 让 dba 可以执行 set_user()
GRANT EXECUTE ON FUNCTION set_user(text) TO dba;
GRANT EXECUTE ON FUNCTION set_user(text,text) TO dba;
GRANT EXECUTE ON FUNCTION set_user_u(text) TO dba;

-- 切换到用户 dba,模拟dba登录
SET SESSION AUTHORIZATION dba;
SELECT SESSION_USER, CURRENT_USER;
---  session_user | current_user
--- --------------+--------------
---  dba          | dba
--- (1 row)

--- 提权, 这里使用 set_user() 提权失败,因为目标用户是 superuser
SELECT set_user('postgres');
--- ERROR:  switching to superuser not allowed
--- HINT:  Use 'set_user_u' to escalate.

--- 提权, 这里使用 set_user_u() 提权成功
SELECT set_user_u('postgres');
---  set_user_u
--- ------------
---  OK
--- (1 row)

--- 查看提权后的用户状态
SELECT SESSION_USER, CURRENT_USER;
---  session_user | current_user
--- --------------+--------------
---  dba          | postgres
--- (1 row)

--- 测试重复 set_user,会失败
SELECT set_user('bob');
--- ERROR:  must reset previous user prior to setting again

--- 提权中,测试 ALTER SYSTEM 会失败
ALTER SYSTEM SET wal_level = minimal;
--- ERROR:  ALTER SYSTEM blocked by set_user config

--- 提权中,测试 COPY PROGRAM 会失败
COPY (select 42) TO PROGRAM 'cat';
--- ERROR:  COPY PROGRAM blocked by set_user config

--- 提权中,测试 SET log_statement 会失败
SET log_statement = 'none';
--- ERROR:  "SET log_statement" blocked by set_user config

--- 恢复初始用户
SELECT reset_user();
SELECT SESSION_USER, CURRENT_USER;
---  session_user | current_user
--- --------------+--------------
---  dba          | dba
--- (1 row)

--- 切换到普通用户bob
SELECT set_user('bob');
SELECT SESSION_USER, CURRENT_USER;
---  session_user | current_user
--- --------------+--------------
---  dba          | bob
--- (1 row)

--- 恢复初始用户
SELECT reset_user();
SELECT SESSION_USER, CURRENT_USER;
---  session_user | current_user
--- --------------+--------------
---  dba          | dba
--- (1 row)

查看 logfile 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 AUDIT 标签。每个用户切换都有一条: LOG: XXX transitioning to YYY 对应。

2026-07-17 13:55:34.905 CST [2670610] LOG:  Role dba transitioning to Superuser Role postgres
2026-07-17 13:55:34.905 CST [2670610] STATEMENT:  SELECT set_user_u('postgres');
2026-07-16 16:01:21.193 CST [2644289] AUDIT: LOG:  statement: SELECT SESSION_USER, CURRENT_USER;
2026-07-16 16:01:43.825 CST [2644289] AUDIT: LOG:  statement: SELECT set_user('bob');
2026-07-16 16:01:43.829 CST [2644289] AUDIT: ERROR:  must reset previous user prior to setting again
2026-07-16 16:01:43.829 CST [2644289] AUDIT: STATEMENT:  SELECT set_user('bob');
2026-07-16 16:02:20.177 CST [2644289] AUDIT: LOG:  statement: ALTER SYSTEM SET wal_level = minimal;
2026-07-16 16:02:20.177 CST [2644289] AUDIT: ERROR:  ALTER SYSTEM blocked by set_user config
2026-07-16 16:02:20.177 CST [2644289] AUDIT: STATEMENT:  ALTER SYSTEM SET wal_level = minimal;
2026-07-17 13:56:26.924 CST [2670610] STATEMENT:  SELECT set_user_u('postgres');
2026-07-17 14:01:12.017 CST [2670610] AUDIT: LOG:  statement: select reset_user();
2026-07-17 14:01:12.017 CST [2670610] AUDIT: LOG:  Superuser Role postgres transitioning to Role dba
2026-07-17 14:18:42.884 CST [2671784] LOG:  Role dba transitioning to Role bob
2026-07-17 14:18:42.884 CST [2671784] STATEMENT:  select set_user('bob');
2026-07-17 14:18:53.301 CST [2671784] LOG:  Role bob transitioning to Role dba
2026-07-17 14:18:53.301 CST [2671784] STATEMENT:  select reset_user();
2026-07-17 14:26:16.737 CST [2671784] LOG:  Role dba transitioning to Role bob
2026-07-17 14:26:16.737 CST [2671784] STATEMENT:  SELECT set_user('bob');

4.4. 清理测试数据库

# 关闭测试数据库
${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile stop

# 删除测试数据库
rm -rf ${TEST_DB_DIR}