|
| 1 | + |
| 2 | +:sectnums: |
| 3 | +:sectnumlevels: 5 |
| 4 | + |
| 5 | += set_user |
| 6 | + |
| 7 | +== 概述 |
| 8 | +set_user 是由 Crunchy Data 开发、隶属 pgaudit 项目的 PostgreSQL 扩展,用于解决一个经典的运维安全矛盾:DBA 需要超级用户权限执行维护任务,但直接发放可登录的 superuser 账号意味着权限过大且行为无从审计——原生的 SET ROLE 既不留日志,还能随手关掉日志设置、无痕切回。 |
| 9 | + |
| 10 | +项目地址:<https://github.com/pgaudit/set_user> |
| 11 | + |
| 12 | +版本:REL4_2_0 |
| 13 | + |
| 14 | +开源协议:PostgreSQL License |
| 15 | + |
| 16 | +== 原理介绍 |
| 17 | + |
| 18 | +PostgreSQL原生的 `SET ROLE` / `SET SESSION AUTHORIZATION` 有两个安全短板:一是提权后可以随手 `SET log_statement = 'none'` 关掉日志、`RESET ROLE` 悄悄切回来,审计留不下痕迹;二是要让 DBA 能干超级用户的活,通常得给他们能直接登录 superuser 账号,权限过大且不可控。 |
| 19 | + |
| 20 | +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 始终保持真实登录者,"谁在什么时候以什么身份做了什么"在日志中一目了然。 |
| 21 | + |
| 22 | +实现上,它以 C 扩展形式通过 shared_preload_libraries 加载,核心依赖三个内核机制:ProcessUtility_hook 拦截危险语句,object_access_hook 封堵函数级后门,事务提交回调保证切换的事务安全。权限控制采用双闸设计——SQL 层的 GRANT EXECUTE 决定谁能调用,配置层的白名单(superuser_allowlist 等)可随时热调整收口。此外还提供带口令锁的 set_user(user, token) 供连接池代持连接时防逃逸,以及不可逆的 set_session_auth() 用于连接移交前的永久降权。 |
| 23 | + |
| 24 | +set_user 并不能阻止超级用户作恶——它的定位是确保任何特权操作都无法逃避审计,配合日志告警系统,构成 PostgreSQL 最小权限运维体系的关键一环。 |
| 25 | + |
| 26 | +== 使用说明 |
| 27 | + |
| 28 | +[NOTE] |
| 29 | +需要将 `set_user` 添加到 `shared_preload_libraries`。如果有其他插件实现 `post-execution hooks` 那么需要将 `set_user` 列在这些插件之前。 |
| 30 | + |
| 31 | +[IMPORTANT] |
| 32 | +superuser 账号(如 postgres)需要设为 NOLOGIN,DBA 用普通账号登录,需要时调用 `set_user_u('postgres')` 提权。 |
| 33 | + |
| 34 | +** 配置介绍 |
| 35 | + |
| 36 | +[cols="1,1,1,1", options="header"] |
| 37 | +|=== |
| 38 | +| 配置项 | 说明 | 示例 | 功能 |
| 39 | +| set_user.block_alter_system | `on` (默认)或者 `off` | `on` | 阻止 ALTER SYSTEM 命令 |
| 40 | +| set_user.block_copy_program | `on` (默认)或者 `off` | `on` | 阻止 COPY PROGRAM 命令 |
| 41 | +| set_user.block_log_statement | `on` (默认)或者 `off` | `on` | 阻止修改 log_statement |
| 42 | +| set_user.nosuperuser_target_allowlist | 通配符 '*' (默认)或字符串 | 'dba1, dba2, +admin_group' | 允许 set_user() 切换到的目标角色名单 |
| 43 | +| set_user.superuser_allowlist | 通配符 '*' (默认)或字符串 | 'dba1, dba2, +admin_group' | 允许调用 set_user_u() 提权的用户名单 |
| 44 | +| set_user.superuser_audit_tag | 字符串 | 'AUDIT' | 日志前缀标签 |
| 45 | +| set_user.exit_on_error | `on` (默认)或者 `off` | `on` | 出现错误时是否退出当前会话 |
| 46 | +|=== |
| 47 | + |
| 48 | + |
| 49 | +** 函数介绍 |
| 50 | + |
| 51 | +[cols="1,1,1,1", options="header"] |
| 52 | +|=== |
| 53 | +| 函数 | 默认权限 | 参数 | 说明 |
| 54 | +| `set_user(text)` | REMOVE FROM PUBLIC | 目标非superuser用户名 | 将当前会话的用户身份切换为指定的非superuser用户 |
| 55 | +| `set_user(text, text)` | REMOVE FROM PUBLIC | 目标非superuser用户名,随机token | 同上,但需要提供一个随机token以增加安全性 |
| 56 | +| `set_user_u(text)` | REMOVE FROM PUBLIC | 目标superuser用户名 | 将当前会话的用户身份切换为指定的superuser用户 |
| 57 | +| `reset_user()` | GRANT TO PUBLIC | 无 | 将当前会话的用户身份恢复为原始用户 |
| 58 | +| `reset_user(text)` | GRANT TO PUBLIC | 随机token | 带口令恢复,但是需要提供对应的token |
| 59 | +| `set_session_auth(text)` | REMOVE FROM PUBLIC | 目标非superuser用户名 | 类似 `SET SESSION AUTHORIZATION` 但是只能切换到普通用户,并且不能切换回来 |
| 60 | +|=== |
| 61 | + |
| 62 | +== 安装 |
| 63 | + |
| 64 | +[TIP] |
| 65 | +源码测试安装环境为 Ubuntu 26.04。需要安装IvorySQL,具体安装步骤请参考:<https://github.com/IvorySQL/IvorySQL> |
| 66 | + |
| 67 | +=== 源码安装 |
| 68 | + |
| 69 | +** 设置IvorySQL安装路径 |
| 70 | +[source,bash] |
| 71 | +---- |
| 72 | +export IVY_BIN_DIR=/usr/local/ivorysql/bin |
| 73 | +export TEST_DB_DIR=/tmp/test_db |
| 74 | +---- |
| 75 | + |
| 76 | +** 获取源码 |
| 77 | +[source,bash] |
| 78 | +---- |
| 79 | +git clone https://github.com/pgaudit/set_user.git |
| 80 | +cd set_user |
| 81 | +git checkout REL4_2_0 |
| 82 | +---- |
| 83 | + |
| 84 | +** 编译安装 |
| 85 | +[source,bash] |
| 86 | +---- |
| 87 | +make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config clean |
| 88 | +make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config |
| 89 | +make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config install |
| 90 | +---- |
| 91 | + |
| 92 | +** 配置 |
| 93 | +[source,ini] |
| 94 | +---- |
| 95 | +shared_preload_libraries = 'set_user' |
| 96 | +---- |
| 97 | + |
| 98 | +=== 创建测试数据库 |
| 99 | + |
| 100 | +[NOTE] |
| 101 | +需要将 `set_user` 添加到 `shared_preload_libraries`。 |
| 102 | + |
| 103 | +** 初始化数据库 |
| 104 | +[source,bash] |
| 105 | +---- |
| 106 | +# 初始化数据库 |
| 107 | +${IVY_BIN_DIR}/initdb -U postgres -D ${TEST_DB_DIR} |
| 108 | +
|
| 109 | +# 将 set_user 添加到 shared_preload_libraries 中,也可以手动配置 |
| 110 | +sed -i "s/\(shared_preload_libraries = '.*\)'/\1, set_user'/g" ${TEST_DB_DIR}/ivorysql.conf |
| 111 | +
|
| 112 | +# 修改PG端口和Oracle兼容端口,避免冲突 |
| 113 | +cat << EOF >> ${TEST_DB_DIR}/ivorysql.conf |
| 114 | +ivorysql.port = 1522 |
| 115 | +port = 5433 |
| 116 | +EOF |
| 117 | +
|
| 118 | +# 启动测试数据库 |
| 119 | +${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile start |
| 120 | +---- |
| 121 | + |
| 122 | +=== 使用测试 |
| 123 | + |
| 124 | +** 连接数据库 |
| 125 | + |
| 126 | +[source,bash] |
| 127 | +---- |
| 128 | +PGHOST=127.0.0.1 PGPORT=1522 PGUSER=postgres $IVY_BIN_DIR/psql |
| 129 | +---- |
| 130 | + |
| 131 | +** 测试示例 |
| 132 | + |
| 133 | +[TIP] |
| 134 | +这里使用默认配置,即白名单为 '*。 |
| 135 | + |
| 136 | +[source,sql] |
| 137 | +---- |
| 138 | +CREATE EXTENSION set_user; |
| 139 | +LOAD 'set_user'; |
| 140 | +
|
| 141 | +--- 创建两个用户 |
| 142 | +CREATE USER dba; |
| 143 | +CREATE USER bob; |
| 144 | +
|
| 145 | +-- 让 dba 可以执行 set_user() |
| 146 | +GRANT EXECUTE ON FUNCTION set_user(text) TO dba; |
| 147 | +GRANT EXECUTE ON FUNCTION set_user(text,text) TO dba; |
| 148 | +GRANT EXECUTE ON FUNCTION set_user_u(text) TO dba; |
| 149 | +
|
| 150 | +-- 切换到用户 dba,模拟dba登录 |
| 151 | +SET SESSION AUTHORIZATION dba; |
| 152 | +SELECT SESSION_USER, CURRENT_USER; |
| 153 | + session_user | current_user |
| 154 | +--------------+-------------- |
| 155 | + dba | dba |
| 156 | +(1 row) |
| 157 | +
|
| 158 | +--- 提权, 这里使用 set_user() 提权失败,因为目标用户是 superuser |
| 159 | +SELECT set_user('postgres'); |
| 160 | +ERROR: switching to superuser not allowed |
| 161 | +HINT: Use 'set_user_u' to escalate. |
| 162 | +
|
| 163 | +--- 提权, 这里使用 set_user_u() 提权成功 |
| 164 | +SELECT set_user_u('postgres'); |
| 165 | + set_user_u |
| 166 | +------------ |
| 167 | + OK |
| 168 | +(1 row) |
| 169 | + |
| 170 | +--- 查看提权后的用户状态 |
| 171 | +SELECT SESSION_USER, CURRENT_USER; |
| 172 | + session_user | current_user |
| 173 | +--------------+-------------- |
| 174 | + dba | postgres |
| 175 | +(1 row) |
| 176 | +
|
| 177 | +--- 测试重复 set_user,会失败 |
| 178 | +SELECT set_user('bob'); |
| 179 | +ERROR: must reset previous user prior to setting again |
| 180 | +
|
| 181 | +--- 提权中,测试 ALTER SYSTEM 会失败 |
| 182 | +ALTER SYSTEM SET wal_level = minimal; |
| 183 | +ERROR: ALTER SYSTEM blocked by set_user config |
| 184 | +
|
| 185 | +--- 提权中,测试 COPY PROGRAM 会失败 |
| 186 | +COPY (select 42) TO PROGRAM 'cat'; |
| 187 | +ERROR: COPY PROGRAM blocked by set_user config |
| 188 | +
|
| 189 | +--- 提权中,测试 SET log_statement 会失败 |
| 190 | +SET log_statement = 'none'; |
| 191 | +ERROR: "SET log_statement" blocked by set_user config |
| 192 | +
|
| 193 | +--- 恢复初始用户 |
| 194 | +SELECT reset_user(); |
| 195 | +SELECT SESSION_USER, CURRENT_USER; |
| 196 | + session_user | current_user |
| 197 | +--------------+-------------- |
| 198 | + dba | dba |
| 199 | +(1 row) |
| 200 | +---- |
| 201 | + |
| 202 | +查看 `logfile` 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 `AUDIT` 标签。 |
| 203 | + |
| 204 | +[source,log] |
| 205 | +---- |
| 206 | +2026-07-16 16:01:21.193 CST [2644289] AUDIT: LOG: statement: SELECT SESSION_USER, CURRENT_USER; |
| 207 | +2026-07-16 16:01:43.825 CST [2644289] AUDIT: LOG: statement: SELECT set_user('bob'); |
| 208 | +2026-07-16 16:01:43.829 CST [2644289] AUDIT: ERROR: must reset previous user prior to setting again |
| 209 | +2026-07-16 16:01:43.829 CST [2644289] AUDIT: STATEMENT: SELECT set_user('bob'); |
| 210 | +2026-07-16 16:02:20.177 CST [2644289] AUDIT: LOG: statement: ALTER SYSTEM SET wal_level = minimal; |
| 211 | +2026-07-16 16:02:20.177 CST [2644289] AUDIT: ERROR: ALTER SYSTEM blocked by set_user config |
| 212 | +2026-07-16 16:02:20.177 CST [2644289] AUDIT: STATEMENT: ALTER SYSTEM SET wal_level = minimal; |
| 213 | +---- |
| 214 | + |
| 215 | +=== 清理测试数据库 |
| 216 | + |
| 217 | +[source,bash] |
| 218 | +---- |
| 219 | +# 关闭测试数据库 |
| 220 | +${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile stop |
| 221 | +
|
| 222 | +# 删除测试数据库 |
| 223 | +rm -rf ${TEST_DB_DIR} |
| 224 | +---- |
0 commit comments