From 0dbd42120290690a762f6f2cfd726ba1be39bbe1 Mon Sep 17 00:00:00 2001 From: hanjianqiao Date: Thu, 16 Jul 2026 16:23:35 +0800 Subject: [PATCH 1/5] add doc for set_user --- .../ecosystem_overview.adoc | 1 + .../master/ecosystem_components/set_user.adoc | 224 ++++++++++++++++++ .../ecosystem_overview.adoc | 1 + .../master/ecosystem_components/set_user.adoc | 223 +++++++++++++++++ 4 files changed, 449 insertions(+) create mode 100644 CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc create mode 100644 EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc index c766670..80131c1 100644 --- a/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc +++ b/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc @@ -40,6 +40,7 @@ IvorySQL 作为一款兼容 Oracle 且基于 PostgreSQL 的高级开源数据库 | 27 | xref:master/ecosystem_components/pg_readonly.adoc[pg_readonly] | 1.0.5 | 可将 PostgreSQL 数据库集群设置为只读 | 系统调试、灾难恢复 | 28 | xref:master/ecosystem_components/zhparser.adoc[zhparser] | master branch | 用于中文全文搜索的PostgreSQL插件,基于SCWS(即:简易中文分词系统)实现了一个中文解析器 | 搜索引擎、关键字提取 | 29 | xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] | 2.58.0 | 可靠的 PostgreSQL 备份和恢复解决方案 | 容灾备份、大库备份、异地/多层容灾 +| 30 | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL 扩展,用于解决运维安全矛盾,确保特权操作无法逃避审计 | 数据库运维安全、权限管理、审计日志 |==== 这些插件均经过 IvorySQL 团队的测试和适配,确保在 IvorySQL 环境下稳定运行。用户可以根据业务需求选择合适的插件,进一步提升数据库系统的能力和灵活性。 diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc new file mode 100644 index 0000000..2a113fa --- /dev/null +++ b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -0,0 +1,224 @@ + +:sectnums: +:sectnumlevels: 5 + += set_user + +== 概述 +set_user 是由 Crunchy Data 开发、隶属 pgaudit 项目的 PostgreSQL 扩展,用于解决一个经典的运维安全矛盾:DBA 需要超级用户权限执行维护任务,但直接发放可登录的 superuser 账号意味着权限过大且行为无从审计——原生的 SET ROLE 既不留日志,还能随手关掉日志设置、无痕切回。 + +项目地址: + +版本:REL4_2_0 + +开源协议:PostgreSQL License + +== 原理介绍 + +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 最小权限运维体系的关键一环。 + +== 使用说明 + +[NOTE] +需要将 `set_user` 添加到 `shared_preload_libraries`。如果有其他插件实现 `post-execution hooks` 那么需要将 `set_user` 列在这些插件之前。 + +[IMPORTANT] +superuser 账号(如 postgres)需要设为 NOLOGIN,DBA 用普通账号登录,需要时调用 `set_user_u('postgres')` 提权。 + +** 配置介绍 + +[cols="1,1,1,1", options="header"] +|=== +| 配置项 | 说明 | 示例 | 功能 +| 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` | 出现错误时是否退出当前会话 +|=== + + +** 函数介绍 + +[cols="1,1,1,1", options="header"] +|=== +| 函数 | 默认权限 | 参数 | 说明 +| `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` 但是只能切换到普通用户,并且不能切换回来 +|=== + +== 安装 + +[TIP] +源码测试安装环境为 Ubuntu 26.04。需要安装IvorySQL,具体安装步骤请参考: + +=== 源码安装 + +** 设置IvorySQL安装路径 +[source,bash] +---- +export IVY_BIN_DIR=/usr/local/ivorysql/bin +export TEST_DB_DIR=/tmp/test_db +---- + +** 获取源码 +[source,bash] +---- +git clone https://github.com/pgaudit/set_user.git +cd set_user +git checkout REL4_2_0 +---- + +** 编译安装 +[source,bash] +---- +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 +---- + +** 配置 +[source,ini] +---- +shared_preload_libraries = 'set_user' +---- + +=== 创建测试数据库 + +[NOTE] +需要将 `set_user` 添加到 `shared_preload_libraries`。 + +** 初始化数据库 +[source,bash] +---- +# 初始化数据库 +${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 +---- + +=== 使用测试 + +** 连接数据库 + +[source,bash] +---- +PGHOST=127.0.0.1 PGPORT=1522 PGUSER=postgres $IVY_BIN_DIR/psql +---- + +** 测试示例 + +[TIP] +这里使用默认配置,即白名单为 '*。 + +[source,sql] +---- +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) +---- + +查看 `logfile` 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 `AUDIT` 标签。 + +[source,log] +---- +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; +---- + +=== 清理测试数据库 + +[source,bash] +---- +# 关闭测试数据库 +${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile stop + +# 删除测试数据库 +rm -rf ${TEST_DB_DIR} +---- \ No newline at end of file diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc index 79180a5..87c8dcf 100644 --- a/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc +++ b/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc @@ -41,6 +41,7 @@ IvorySQL, as an advanced open-source database compatible with Oracle and based o |*27*| xref:master/ecosystem_components/pg_readonly_en.adoc[pg_read_only] | 1.0.5 | Allows to set all PostgreSQL cluster databases read only. | System debugging、disaster recevory |*28*| xref:master/ecosystem_components/zhparser_en.adoc[zhparser] | master branch | PostgreSQL extension for full-text search of Chinese language (Mandarin Chinese). It implements a Chinese language parser base on the | Search engine、keyword extraction |*29*| xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] | 2.58.0 | pgBackRest is a reliable backup and restore solution for PostgreSQL that seamlessly scales up to the largest databases and workloads | Disaster recovery backup, large database backup, off-site/multi-tier disaster recovery +| *30* | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL extension that addresses operational security dilemmas, ensuring privileged operations cannot escape auditing | Database operational security, privilege management, audit logging |==== These plugins have all been tested and adapted by the IvorySQL team to ensure stable operation in the IvorySQL environment. Users can select appropriate plugins based on business needs to further enhance the capabilities and flexibility of the database system. diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc new file mode 100644 index 0000000..656f19e --- /dev/null +++ b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -0,0 +1,223 @@ +:sectnums: +:sectnumlevels: 5 + += set_user + +== Overview +set_user is a PostgreSQL extension developed by Crunchy Data as part of the pgaudit project. It addresses a classic operational security dilemma: DBAs need superuser privileges to perform maintenance tasks, but handing out login-enabled superuser accounts grants excessive privileges and leaves their actions unauditable—the native SET ROLE neither writes to the log nor prevents a user from casually disabling log settings and switching back without a trace. + +Project URL: + +Version: REL4_2_0 + +License: PostgreSQL License + +== How It Works + +PostgreSQL's native `SET ROLE` / `SET SESSION AUTHORIZATION` has two security weaknesses. First, after escalating privileges a user can casually run `SET log_statement = 'none'` to disable logging, then `RESET ROLE` to quietly switch back, leaving no audit trail. Second, to let DBAs perform superuser tasks you typically have to give them direct login access to a superuser account—too much privilege, and uncontrollable. + +set_user's approach is "don't forbid escalation, but make every escalation traceable and undeniable." Once deployed, all superuser accounts can be set to NOLOGIN; DBAs log in with ordinary accounts and call set_user_u('postgres') to escalate when needed. Throughout the entire window from escalation until reset_user() restores the original identity: the role switch is written to the log, log_statement is forced to all so every SQL statement is persisted, and the log prefix automatically gets an AUDIT tag appended for easy filtering and alerting. At the same time, ALTER SYSTEM, COPY PROGRAM, SET log_statement, SET ROLE, the set_config() backdoor, and every other channel that could undermine auditing or allow identity escape are all blocked. Because session_user always remains the real logged-in user, "who did what, when, and under which identity" is plainly visible in the log. + +In terms of implementation, it is loaded as a C extension via shared_preload_libraries and relies on three core kernel mechanisms: the ProcessUtility_hook intercepts dangerous statements, the object_access_hook seals off function-level backdoors, and a transaction commit callback ensures the switch is transaction-safe. Privilege control uses a dual-gate design—the SQL-layer GRANT EXECUTE determines who may call the functions, while the configuration-layer allowlists (superuser_allowlist, etc.) can be hot-adjusted at any time to tighten access. It also provides a token-locked set_user(user, token) to prevent escape when a connection pool holds connections on behalf of others, as well as an irreversible set_session_auth() for permanent privilege reduction before handing off a connection. + +set_user cannot stop a superuser from acting maliciously—its purpose is to ensure that no privileged operation can escape auditing. Combined with a log-alerting system, it forms a key part of a least-privilege operations framework for PostgreSQL. + +== Usage + +[NOTE] +You need to add `set_user` to `shared_preload_libraries`. If other plugins implement `post-execution hooks`, then `set_user` must be listed before those plugins. + +[IMPORTANT] +Superuser accounts (such as postgres) should be set to NOLOGIN. DBAs log in with an ordinary account and call `set_user_u('postgres')` to escalate when needed. + +** Configuration + +[cols="1,1,1,1", options="header"] +|=== +| Setting | Description | Example | Function +| set_user.block_alter_system | `on` (default) or `off` | `on` | Block the ALTER SYSTEM command +| set_user.block_copy_program | `on` (default) or `off` | `on` | Block the COPY PROGRAM command +| set_user.block_log_statement | `on` (default) or `off` | `on` | Block changes to log_statement +| set_user.nosuperuser_target_allowlist | wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of target roles that set_user() is allowed to switch to +| set_user.superuser_allowlist | wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of users allowed to call set_user_u() to escalate +| set_user.superuser_audit_tag | string | 'AUDIT' | Log prefix tag +| set_user.exit_on_error | `on` (default) or `off` | `on` | Whether to terminate the current session when an error occurs +|=== + + +** Functions + +[cols="1,1,1,1", options="header"] +|=== +| Function | Default Privilege | Parameters | Description +| `set_user(text)` | REMOVE FROM PUBLIC | target non-superuser username | Switch the current session's user identity to the specified non-superuser user +| `set_user(text, text)` | REMOVE FROM PUBLIC | target non-superuser username, random token | Same as above, but requires a random token for added security +| `set_user_u(text)` | REMOVE FROM PUBLIC | target superuser username | Switch the current session's user identity to the specified superuser user +| `reset_user()` | GRANT TO PUBLIC | none | Restore the current session's user identity to the original user +| `reset_user(text)` | GRANT TO PUBLIC | random token | Token-protected restore, requires the corresponding token +| `set_session_auth(text)` | REMOVE FROM PUBLIC | target non-superuser username | Similar to `SET SESSION AUTHORIZATION`, but can only switch to an ordinary user and cannot switch back +|=== + +== Installation + +[TIP] +The source-build test environment is Ubuntu 26.04. IvorySQL must be installed; for detailed installation steps, see: + +=== Source Installation + +** Set the IvorySQL installation path +[source,bash] +---- +export IVY_BIN_DIR=/usr/local/ivorysql/bin +export TEST_DB_DIR=/tmp/test_db +---- + +** Get the source code +[source,bash] +---- +git clone https://github.com/pgaudit/set_user.git +cd set_user +git checkout REL4_2_0 +---- + +** Build and install +[source,bash] +---- +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 +---- + +** Configure +[source,ini] +---- +shared_preload_libraries = 'set_user' +---- + +=== Create a Test Database + +[NOTE] +You need to add `set_user` to `shared_preload_libraries`. + +** Initialize the database +[source,bash] +---- +# Initialize the database +${IVY_BIN_DIR}/initdb -U postgres -D ${TEST_DB_DIR} + +# Add set_user to shared_preload_libraries (this can also be configured manually) +sed -i "s/\(shared_preload_libraries = '.*\)'/\1, set_user'/g" ${TEST_DB_DIR}/ivorysql.conf + +# Change the PG port and the Oracle-compatible port to avoid conflicts +cat << EOF >> ${TEST_DB_DIR}/ivorysql.conf +ivorysql.port = 1522 +port = 5433 +EOF + +# Start the test database +${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile start +---- + +=== Usage Test + +** Connect to the database + +[source,bash] +---- +PGHOST=127.0.0.1 PGPORT=1522 PGUSER=postgres $IVY_BIN_DIR/psql +---- + +** Testing Examples + +[TIP] +This uses the default configuration, i.e. the allowlist is '*'. + +[source,sql] +---- +CREATE EXTENSION set_user; +LOAD 'set_user'; + +--- Create two users +CREATE USER dba; +CREATE USER bob; + +-- Allow dba to execute 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; + +-- Switch to user dba to simulate a dba login +SET SESSION AUTHORIZATION dba; +SELECT SESSION_USER, CURRENT_USER; + session_user | current_user +--------------+-------------- + dba | dba +(1 row) + +--- Escalate: using set_user() here fails because the target user is a superuser +SELECT set_user('postgres'); +ERROR: switching to superuser not allowed +HINT: Use 'set_user_u' to escalate. + +--- Escalate: using set_user_u() here succeeds +SELECT set_user_u('postgres'); + set_user_u +------------ + OK +(1 row) + +--- Check the user state after escalation +SELECT SESSION_USER, CURRENT_USER; + session_user | current_user +--------------+-------------- + dba | postgres +(1 row) + +--- Test calling set_user again; this fails +SELECT set_user('bob'); +ERROR: must reset previous user prior to setting again + +--- While escalated, test that ALTER SYSTEM fails +ALTER SYSTEM SET wal_level = minimal; +ERROR: ALTER SYSTEM blocked by set_user config + +--- While escalated, test that COPY PROGRAM fails +COPY (select 42) TO PROGRAM 'cat'; +ERROR: COPY PROGRAM blocked by set_user config + +--- While escalated, test that SET log_statement fails +SET log_statement = 'none'; +ERROR: "SET log_statement" blocked by set_user config + +--- Restore the original user +SELECT reset_user(); +SELECT SESSION_USER, CURRENT_USER; + session_user | current_user +--------------+-------------- + dba | dba +(1 row) +---- + +Checking the `logfile` shows that all operations after escalation were recorded, and the log prefix carries the `AUDIT` tag. + +[source,log] +---- +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; +---- + +=== Clean Up the Test Database + +[source,bash] +---- +# Stop the test database +${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile stop + +# Delete the test database +rm -rf ${TEST_DB_DIR} +---- \ No newline at end of file From 69ead1181ebcf6ce3269c73f3ea75c3832d48099 Mon Sep 17 00:00:00 2001 From: hanjianqiao Date: Thu, 16 Jul 2026 16:33:29 +0800 Subject: [PATCH 2/5] add doc for set_user --- CN/modules/ROOT/nav.adoc | 1 + EN/modules/ROOT/nav.adoc | 1 + 2 files changed, 2 insertions(+) diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index 991c4af..473ba5e 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -73,6 +73,7 @@ *** xref:master/ecosystem_components/pg_readonly.adoc[pg_readonly] *** xref:master/ecosystem_components/zhparser.adoc[zhparser] *** xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] +*** xref:master/ecosystem_components/set_user.adoc[set_user] * 监控运维 ** xref:master/getting-started/daily_monitoring.adoc[日常监控] ** xref:master/getting-started/daily_maintenance.adoc[日常维护] diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index b071b69..8194fcd 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -73,6 +73,7 @@ *** xref:master/ecosystem_components/pg_readonly_en.adoc[pg_readonly] *** xref:master/ecosystem_components/zhparser_en.adoc[zhparser] *** xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] +*** xref:master/ecosystem_components/set_user.adoc[set_user] * Monitor and O&M ** xref:master/getting-started/daily_monitoring.adoc[Monitoring] ** xref:master/getting-started/daily_maintenance.adoc[Maintenance] From af6d9d14c1285985bf0b60f329093268ddaa1161 Mon Sep 17 00:00:00 2001 From: hanjianqiao Date: Thu, 16 Jul 2026 16:36:49 +0800 Subject: [PATCH 3/5] add doc for set_user --- .../master/ecosystem_components/set_user.adoc | 44 +++++++++---------- .../master/ecosystem_components/set_user.adoc | 44 +++++++++---------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index 2a113fa..530169c 100644 --- a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -150,53 +150,53 @@ 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) +--- 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. +--- 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) +--- set_user_u +--- ------------ +--- OK +--- (1 row) --- 查看提权后的用户状态 SELECT SESSION_USER, CURRENT_USER; - session_user | current_user ---------------+-------------- - dba | postgres -(1 row) +--- session_user | current_user +--- --------------+-------------- +--- dba | postgres +--- (1 row) --- 测试重复 set_user,会失败 SELECT set_user('bob'); -ERROR: must reset previous user prior to setting again +--- 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 +--- ERROR: ALTER SYSTEM blocked by set_user config --- 提权中,测试 COPY PROGRAM 会失败 COPY (select 42) TO PROGRAM 'cat'; -ERROR: COPY PROGRAM blocked by set_user config +--- ERROR: COPY PROGRAM blocked by set_user config --- 提权中,测试 SET log_statement 会失败 SET log_statement = 'none'; -ERROR: "SET log_statement" blocked by set_user config +--- 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) +--- session_user | current_user +--- --------------+-------------- +--- dba | dba +--- (1 row) ---- 查看 `logfile` 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 `AUDIT` 标签。 diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index 656f19e..493be11 100644 --- a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -149,53 +149,53 @@ GRANT EXECUTE ON FUNCTION set_user_u(text) TO dba; -- Switch to user dba to simulate a dba login SET SESSION AUTHORIZATION dba; SELECT SESSION_USER, CURRENT_USER; - session_user | current_user ---------------+-------------- - dba | dba -(1 row) +--- session_user | current_user +--- --------------+-------------- +--- dba | dba +--- (1 row) --- Escalate: using set_user() here fails because the target user is a superuser SELECT set_user('postgres'); -ERROR: switching to superuser not allowed -HINT: Use 'set_user_u' to escalate. +--- ERROR: switching to superuser not allowed +--- HINT: Use 'set_user_u' to escalate. --- Escalate: using set_user_u() here succeeds SELECT set_user_u('postgres'); - set_user_u ------------- - OK -(1 row) +--- set_user_u +--- ------------ +--- OK +--- (1 row) --- Check the user state after escalation SELECT SESSION_USER, CURRENT_USER; - session_user | current_user ---------------+-------------- - dba | postgres -(1 row) +--- session_user | current_user +--- --------------+-------------- +--- dba | postgres +--- (1 row) --- Test calling set_user again; this fails SELECT set_user('bob'); -ERROR: must reset previous user prior to setting again +--- ERROR: must reset previous user prior to setting again --- While escalated, test that ALTER SYSTEM fails ALTER SYSTEM SET wal_level = minimal; -ERROR: ALTER SYSTEM blocked by set_user config +--- ERROR: ALTER SYSTEM blocked by set_user config --- While escalated, test that COPY PROGRAM fails COPY (select 42) TO PROGRAM 'cat'; -ERROR: COPY PROGRAM blocked by set_user config +--- ERROR: COPY PROGRAM blocked by set_user config --- While escalated, test that SET log_statement fails SET log_statement = 'none'; -ERROR: "SET log_statement" blocked by set_user config +--- ERROR: "SET log_statement" blocked by set_user config --- Restore the original user SELECT reset_user(); SELECT SESSION_USER, CURRENT_USER; - session_user | current_user ---------------+-------------- - dba | dba -(1 row) +--- session_user | current_user +--- --------------+-------------- +--- dba | dba +--- (1 row) ---- Checking the `logfile` shows that all operations after escalation were recorded, and the log prefix carries the `AUDIT` tag. From 0fda680209bbfa11041839810ef55750086fab46 Mon Sep 17 00:00:00 2001 From: hanjianqiao Date: Fri, 17 Jul 2026 14:42:20 +0800 Subject: [PATCH 4/5] update doc --- .../ecosystem_overview.adoc | 2 +- .../master/ecosystem_components/set_user.adoc | 31 ++++- .../ecosystem_overview.adoc | 2 +- .../master/ecosystem_components/set_user.adoc | 111 +++++++++++------- 4 files changed, 100 insertions(+), 46 deletions(-) diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc index 80131c1..074ce67 100644 --- a/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc +++ b/CN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc @@ -40,7 +40,7 @@ IvorySQL 作为一款兼容 Oracle 且基于 PostgreSQL 的高级开源数据库 | 27 | xref:master/ecosystem_components/pg_readonly.adoc[pg_readonly] | 1.0.5 | 可将 PostgreSQL 数据库集群设置为只读 | 系统调试、灾难恢复 | 28 | xref:master/ecosystem_components/zhparser.adoc[zhparser] | master branch | 用于中文全文搜索的PostgreSQL插件,基于SCWS(即:简易中文分词系统)实现了一个中文解析器 | 搜索引擎、关键字提取 | 29 | xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] | 2.58.0 | 可靠的 PostgreSQL 备份和恢复解决方案 | 容灾备份、大库备份、异地/多层容灾 -| 30 | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL 扩展,用于解决运维安全矛盾,确保特权操作无法逃避审计 | 数据库运维安全、权限管理、审计日志 +| 30 | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL 安全审计扩展,可控角色切换,支持白名单、强制审计、拦截高危操作 | 可控角色切换、权限管理、审计日志 |==== 这些插件均经过 IvorySQL 团队的测试和适配,确保在 IvorySQL 环境下稳定运行。用户可以根据业务需求选择合适的插件,进一步提升数据库系统的能力和灵活性。 diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index 530169c..d5cafb6 100644 --- a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -5,7 +5,7 @@ = set_user == 概述 -set_user 是由 Crunchy Data 开发、隶属 pgaudit 项目的 PostgreSQL 扩展,用于解决一个经典的运维安全矛盾:DBA 需要超级用户权限执行维护任务,但直接发放可登录的 superuser 账号意味着权限过大且行为无从审计——原生的 SET ROLE 既不留日志,还能随手关掉日志设置、无痕切回。 +set_user 是 PostgreSQL 安全审计扩展,由 pgaudit 项目维护,优化原生角色切换能力。支持普通用户间互切,也可受控切换至超级用户;可配置白名单限定允许切换的账号,所有切换操作留存审计日志,切换到超级用户时强制完整记录 SQL,拦截修改数据库配置、调用系统命令等高风险操作,规范临时提权,满足等保合规,部署需预加载插件并重启数据库,再建扩展使用。 项目地址: @@ -190,6 +190,22 @@ COPY (select 42) TO PROGRAM 'cat'; 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; @@ -199,10 +215,12 @@ SELECT SESSION_USER, CURRENT_USER; --- (1 row) ---- -查看 `logfile` 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 `AUDIT` 标签。 +查看 `logfile` 能看到提权后的所有操作都被记录了下来,并且日志前缀带有 `AUDIT` 标签。每个用户切换都有一条: `LOG: XXX transitioning to YYY` 对应。 [source,log] ---- +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 @@ -210,6 +228,15 @@ SELECT SESSION_USER, CURRENT_USER; 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'); ---- === 清理测试数据库 diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc index 87c8dcf..4e3270c 100644 --- a/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc +++ b/EN/modules/ROOT/pages/master/ecosystem_components/ecosystem_overview.adoc @@ -41,7 +41,7 @@ IvorySQL, as an advanced open-source database compatible with Oracle and based o |*27*| xref:master/ecosystem_components/pg_readonly_en.adoc[pg_read_only] | 1.0.5 | Allows to set all PostgreSQL cluster databases read only. | System debugging、disaster recevory |*28*| xref:master/ecosystem_components/zhparser_en.adoc[zhparser] | master branch | PostgreSQL extension for full-text search of Chinese language (Mandarin Chinese). It implements a Chinese language parser base on the | Search engine、keyword extraction |*29*| xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] | 2.58.0 | pgBackRest is a reliable backup and restore solution for PostgreSQL that seamlessly scales up to the largest databases and workloads | Disaster recovery backup, large database backup, off-site/multi-tier disaster recovery -| *30* | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL extension that addresses operational security dilemmas, ensuring privileged operations cannot escape auditing | Database operational security, privilege management, audit logging +| *30* | xref:master/ecosystem_components/set_user.adoc[set_user] | REL4_2_0 | PostgreSQL security auditing extension with controlled role switching, supporting allowlists, enforced auditing, and blocking of high-risk operations | Controlled role switching, privilege management, audit logging |==== These plugins have all been tested and adapted by the IvorySQL team to ensure stable operation in the IvorySQL environment. Users can select appropriate plugins based on business needs to further enhance the capabilities and flexibility of the database system. diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index 493be11..c150ed9 100644 --- a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -4,9 +4,9 @@ = set_user == Overview -set_user is a PostgreSQL extension developed by Crunchy Data as part of the pgaudit project. It addresses a classic operational security dilemma: DBAs need superuser privileges to perform maintenance tasks, but handing out login-enabled superuser accounts grants excessive privileges and leaves their actions unauditable—the native SET ROLE neither writes to the log nor prevents a user from casually disabling log settings and switching back without a trace. +set_user is a PostgreSQL security auditing extension maintained by the pgaudit project that enhances the native role-switching capability. It supports switching between ordinary users as well as controlled escalation to superuser; configurable allowlists restrict which accounts may be switched to, all switching operations are recorded in the audit log, full SQL logging is enforced while escalated to superuser, and high-risk operations such as modifying database configuration or invoking system commands are blocked. It standardizes temporary privilege escalation and helps satisfy security-compliance requirements (e.g., China's MLPS). Deployment requires preloading the plugin and restarting the database, then creating the extension before use. -Project URL: +Project page: Version: REL4_2_0 @@ -14,34 +14,34 @@ License: PostgreSQL License == How It Works -PostgreSQL's native `SET ROLE` / `SET SESSION AUTHORIZATION` has two security weaknesses. First, after escalating privileges a user can casually run `SET log_statement = 'none'` to disable logging, then `RESET ROLE` to quietly switch back, leaving no audit trail. Second, to let DBAs perform superuser tasks you typically have to give them direct login access to a superuser account—too much privilege, and uncontrollable. +PostgreSQL's native `SET ROLE` / `SET SESSION AUTHORIZATION` has two security weaknesses: first, after escalating privileges a user can casually run `SET log_statement = 'none'` to turn off logging, or `RESET ROLE` to quietly switch back, leaving no audit trail; second, to let DBAs perform superuser work, they typically must be allowed to log in directly as a superuser account, granting excessive and uncontrolled privileges. -set_user's approach is "don't forbid escalation, but make every escalation traceable and undeniable." Once deployed, all superuser accounts can be set to NOLOGIN; DBAs log in with ordinary accounts and call set_user_u('postgres') to escalate when needed. Throughout the entire window from escalation until reset_user() restores the original identity: the role switch is written to the log, log_statement is forced to all so every SQL statement is persisted, and the log prefix automatically gets an AUDIT tag appended for easy filtering and alerting. At the same time, ALTER SYSTEM, COPY PROGRAM, SET log_statement, SET ROLE, the set_config() backdoor, and every other channel that could undermine auditing or allow identity escape are all blocked. Because session_user always remains the real logged-in user, "who did what, when, and under which identity" is plainly visible in the log. +set_user's approach is "don't forbid privilege escalation, but make every escalation fully traceable and non-repudiable." Once deployed, all superuser accounts can be set to NOLOGIN; DBAs log in with ordinary accounts and call set_user_u('postgres') when escalation is needed. Throughout the window from escalation until reset_user() restores the original identity: the role switch is written to the log, log_statement is forcibly set to all so that every SQL statement is persisted, and the log prefix automatically gains an AUDIT tag for easy filtering and alerting; at the same time, every channel that could undermine auditing or escape the identity — ALTER SYSTEM, COPY PROGRAM, SET log_statement, SET ROLE, and the set_config() backdoor — is blocked. Because session_user always remains the real login user, "who did what, when, and under which identity" is plainly visible in the logs. -In terms of implementation, it is loaded as a C extension via shared_preload_libraries and relies on three core kernel mechanisms: the ProcessUtility_hook intercepts dangerous statements, the object_access_hook seals off function-level backdoors, and a transaction commit callback ensures the switch is transaction-safe. Privilege control uses a dual-gate design—the SQL-layer GRANT EXECUTE determines who may call the functions, while the configuration-layer allowlists (superuser_allowlist, etc.) can be hot-adjusted at any time to tighten access. It also provides a token-locked set_user(user, token) to prevent escape when a connection pool holds connections on behalf of others, as well as an irreversible set_session_auth() for permanent privilege reduction before handing off a connection. +Implementation-wise, it is a C extension loaded via shared_preload_libraries and relies on three kernel mechanisms: ProcessUtility_hook to intercept dangerous statements, object_access_hook to block function-level backdoors, and transaction commit callbacks to guarantee transactional safety of the switch. Access control uses a dual-gate design — at the SQL layer, GRANT EXECUTE determines who may call the functions, while at the configuration layer, allowlists (superuser_allowlist, etc.) can be hot-adjusted at any time to tighten control. In addition, it provides a token-locked set_user(user, token) to prevent escape when a connection pooler holds connections on behalf of clients, and the irreversible set_session_auth() for permanent privilege drop before handing over a connection. -set_user cannot stop a superuser from acting maliciously—its purpose is to ensure that no privileged operation can escape auditing. Combined with a log-alerting system, it forms a key part of a least-privilege operations framework for PostgreSQL. +set_user cannot prevent a superuser from acting maliciously — its role is to ensure that no privileged operation can evade auditing. Combined with a log alerting system, it forms a key component of a least-privilege operations framework for PostgreSQL. -== Usage +== Usage Notes [NOTE] -You need to add `set_user` to `shared_preload_libraries`. If other plugins implement `post-execution hooks`, then `set_user` must be listed before those plugins. +`set_user` must be added to `shared_preload_libraries`. If other plugins implement `post-execution hooks`, `set_user` must be listed before them. [IMPORTANT] -Superuser accounts (such as postgres) should be set to NOLOGIN. DBAs log in with an ordinary account and call `set_user_u('postgres')` to escalate when needed. +Superuser accounts (e.g., postgres) should be set to NOLOGIN; DBAs log in with ordinary accounts and call `set_user_u('postgres')` when escalation is needed. -** Configuration +** Configuration options [cols="1,1,1,1", options="header"] |=== -| Setting | Description | Example | Function -| set_user.block_alter_system | `on` (default) or `off` | `on` | Block the ALTER SYSTEM command -| set_user.block_copy_program | `on` (default) or `off` | `on` | Block the COPY PROGRAM command +| Option | Description | Example | Function +| set_user.block_alter_system | `on` (default) or `off` | `on` | Block ALTER SYSTEM commands +| set_user.block_copy_program | `on` (default) or `off` | `on` | Block COPY PROGRAM commands | set_user.block_log_statement | `on` (default) or `off` | `on` | Block changes to log_statement -| set_user.nosuperuser_target_allowlist | wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of target roles that set_user() is allowed to switch to -| set_user.superuser_allowlist | wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of users allowed to call set_user_u() to escalate -| set_user.superuser_audit_tag | string | 'AUDIT' | Log prefix tag -| set_user.exit_on_error | `on` (default) or `off` | `on` | Whether to terminate the current session when an error occurs +| set_user.nosuperuser_target_allowlist | Wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of target roles that set_user() may switch to +| set_user.superuser_allowlist | Wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of users allowed to call set_user_u() for escalation +| set_user.superuser_audit_tag | String | 'AUDIT' | Log prefix tag +| set_user.exit_on_error | `on` (default) or `off` | `on` | Whether to exit the current session on error |=== @@ -49,21 +49,21 @@ Superuser accounts (such as postgres) should be set to NOLOGIN. DBAs log in with [cols="1,1,1,1", options="header"] |=== -| Function | Default Privilege | Parameters | Description -| `set_user(text)` | REMOVE FROM PUBLIC | target non-superuser username | Switch the current session's user identity to the specified non-superuser user -| `set_user(text, text)` | REMOVE FROM PUBLIC | target non-superuser username, random token | Same as above, but requires a random token for added security -| `set_user_u(text)` | REMOVE FROM PUBLIC | target superuser username | Switch the current session's user identity to the specified superuser user -| `reset_user()` | GRANT TO PUBLIC | none | Restore the current session's user identity to the original user -| `reset_user(text)` | GRANT TO PUBLIC | random token | Token-protected restore, requires the corresponding token -| `set_session_auth(text)` | REMOVE FROM PUBLIC | target non-superuser username | Similar to `SET SESSION AUTHORIZATION`, but can only switch to an ordinary user and cannot switch back +| Function | Default privilege | Parameters | Description +| `set_user(text)` | REMOVE FROM PUBLIC | Target non-superuser username | Switch the current session's user identity to the specified non-superuser +| `set_user(text, text)` | REMOVE FROM PUBLIC | Target non-superuser username, random token | Same as above, but requires a random token for added security +| `set_user_u(text)` | REMOVE FROM PUBLIC | Target superuser username | Switch the current session's user identity to the specified superuser +| `reset_user()` | GRANT TO PUBLIC | None | Restore the current session's user identity to the original user +| `reset_user(text)` | GRANT TO PUBLIC | Random token | Token-protected restore; the matching token must be supplied +| `set_session_auth(text)` | REMOVE FROM PUBLIC | Target non-superuser username | Similar to `SET SESSION AUTHORIZATION`, but can only switch to an ordinary user and cannot be reverted |=== == Installation [TIP] -The source-build test environment is Ubuntu 26.04. IvorySQL must be installed; for detailed installation steps, see: +The source build was tested on Ubuntu 26.04. IvorySQL must be installed; for installation steps see: -=== Source Installation +=== Building from Source ** Set the IvorySQL installation path [source,bash] @@ -88,16 +88,16 @@ make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config make USE_PGXS=1 PG_CONFIG=${IVY_BIN_DIR}/pg_config install ---- -** Configure +** Configuration [source,ini] ---- shared_preload_libraries = 'set_user' ---- -=== Create a Test Database +=== Creating a Test Database [NOTE] -You need to add `set_user` to `shared_preload_libraries`. +`set_user` must be added to `shared_preload_libraries`. ** Initialize the database [source,bash] @@ -105,7 +105,7 @@ You need to add `set_user` to `shared_preload_libraries`. # Initialize the database ${IVY_BIN_DIR}/initdb -U postgres -D ${TEST_DB_DIR} -# Add set_user to shared_preload_libraries (this can also be configured manually) +# Add set_user to shared_preload_libraries (this can also be done manually) sed -i "s/\(shared_preload_libraries = '.*\)'/\1, set_user'/g" ${TEST_DB_DIR}/ivorysql.conf # Change the PG port and the Oracle-compatible port to avoid conflicts @@ -127,10 +127,10 @@ ${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile start PGHOST=127.0.0.1 PGPORT=1522 PGUSER=postgres $IVY_BIN_DIR/psql ---- -** Testing Examples +** Test examples [TIP] -This uses the default configuration, i.e. the allowlist is '*'. +The default configuration is used here, i.e., the allowlist is '*'. [source,sql] ---- @@ -146,7 +146,7 @@ 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; --- Switch to user dba to simulate a dba login +-- Switch to user dba to simulate a DBA login SET SESSION AUTHORIZATION dba; SELECT SESSION_USER, CURRENT_USER; --- session_user | current_user @@ -154,12 +154,12 @@ SELECT SESSION_USER, CURRENT_USER; --- dba | dba --- (1 row) ---- Escalate: using set_user() here fails because the target user is a superuser +--- Escalate privileges: using set_user() here fails because the target user is a superuser SELECT set_user('postgres'); --- ERROR: switching to superuser not allowed --- HINT: Use 'set_user_u' to escalate. ---- Escalate: using set_user_u() here succeeds +--- Escalate privileges: using set_user_u() here succeeds SELECT set_user_u('postgres'); --- set_user_u --- ------------ @@ -173,22 +173,38 @@ SELECT SESSION_USER, CURRENT_USER; --- dba | postgres --- (1 row) ---- Test calling set_user again; this fails +--- Test a repeated set_user; it fails SELECT set_user('bob'); --- ERROR: must reset previous user prior to setting again ---- While escalated, test that ALTER SYSTEM fails +--- While escalated, ALTER SYSTEM fails ALTER SYSTEM SET wal_level = minimal; --- ERROR: ALTER SYSTEM blocked by set_user config ---- While escalated, test that COPY PROGRAM fails +--- While escalated, COPY PROGRAM fails COPY (select 42) TO PROGRAM 'cat'; --- ERROR: COPY PROGRAM blocked by set_user config ---- While escalated, test that SET log_statement fails +--- While escalated, SET log_statement fails SET log_statement = 'none'; --- ERROR: "SET log_statement" blocked by set_user config +--- Restore the original user +SELECT reset_user(); +SELECT SESSION_USER, CURRENT_USER; +--- session_user | current_user +--- --------------+-------------- +--- dba | dba +--- (1 row) + +--- Switch to the ordinary user bob +SELECT set_user('bob'); +SELECT SESSION_USER, CURRENT_USER; +--- session_user | current_user +--- --------------+-------------- +--- dba | bob +--- (1 row) + --- Restore the original user SELECT reset_user(); SELECT SESSION_USER, CURRENT_USER; @@ -198,10 +214,12 @@ SELECT SESSION_USER, CURRENT_USER; --- (1 row) ---- -Checking the `logfile` shows that all operations after escalation were recorded, and the log prefix carries the `AUDIT` tag. +Inspecting `logfile` shows that every operation performed after escalation was recorded, with the `AUDIT` tag in the log prefix. Each user switch has a corresponding line: `LOG: XXX transitioning to YYY`. [source,log] ---- +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 @@ -209,15 +227,24 @@ Checking the `logfile` shows that all operations after escalation were recorded, 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'); ---- -=== Clean Up the Test Database +=== Cleaning Up the Test Database [source,bash] ---- # Stop the test database ${IVY_BIN_DIR}/pg_ctl -D ${TEST_DB_DIR} -l ${TEST_DB_DIR}/logfile stop -# Delete the test database +# Remove the test database rm -rf ${TEST_DB_DIR} ---- \ No newline at end of file From f183c7f5195d6aa2c9ad985dc82fc0a399a4ae8f Mon Sep 17 00:00:00 2001 From: hanjianqiao Date: Fri, 17 Jul 2026 15:26:16 +0800 Subject: [PATCH 5/5] Update the wording: change 'role' to 'user' consistently --- .../ROOT/pages/master/ecosystem_components/set_user.adoc | 6 +++--- .../ROOT/pages/master/ecosystem_components/set_user.adoc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index d5cafb6..267d924 100644 --- a/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/CN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -5,7 +5,7 @@ = set_user == 概述 -set_user 是 PostgreSQL 安全审计扩展,由 pgaudit 项目维护,优化原生角色切换能力。支持普通用户间互切,也可受控切换至超级用户;可配置白名单限定允许切换的账号,所有切换操作留存审计日志,切换到超级用户时强制完整记录 SQL,拦截修改数据库配置、调用系统命令等高风险操作,规范临时提权,满足等保合规,部署需预加载插件并重启数据库,再建扩展使用。 +set_user 是 PostgreSQL 安全审计扩展,由 pgaudit 项目维护,优化原生用户切换能力。支持普通用户间互切,也可受控切换至超级用户;可配置白名单限定允许切换的用户,所有切换操作留存审计日志,切换到超级用户时强制完整记录 SQL,拦截修改数据库配置、调用系统命令等高风险操作,规范临时提权,部署需预加载插件并重启数据库,再创建扩展使用。 项目地址: @@ -17,7 +17,7 @@ set_user 是 PostgreSQL 安全审计扩展,由 pgaudit 项目维护,优化 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 始终保持真实登录者,"谁在什么时候以什么身份做了什么"在日志中一目了然。 +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() 用于连接移交前的永久降权。 @@ -39,7 +39,7 @@ superuser 账号(如 postgres)需要设为 NOLOGIN,DBA 用普通账号登 | 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.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` | 出现错误时是否退出当前会话 diff --git a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc index c150ed9..7eeb7d6 100644 --- a/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc +++ b/EN/modules/ROOT/pages/master/ecosystem_components/set_user.adoc @@ -4,7 +4,7 @@ = set_user == Overview -set_user is a PostgreSQL security auditing extension maintained by the pgaudit project that enhances the native role-switching capability. It supports switching between ordinary users as well as controlled escalation to superuser; configurable allowlists restrict which accounts may be switched to, all switching operations are recorded in the audit log, full SQL logging is enforced while escalated to superuser, and high-risk operations such as modifying database configuration or invoking system commands are blocked. It standardizes temporary privilege escalation and helps satisfy security-compliance requirements (e.g., China's MLPS). Deployment requires preloading the plugin and restarting the database, then creating the extension before use. +set_user is a PostgreSQL security auditing extension maintained by the pgaudit project that enhances the native user-switching capability. It supports switching between ordinary users as well as controlled escalation to superuser; configurable allowlists restrict which users may be switched to, all switching operations are recorded in the audit log, full SQL logging is enforced when switching to superuser, and high-risk operations such as modifying database configuration or invoking system commands are blocked. It standardizes temporary privilege escalation. Deployment requires preloading the plugin and restarting the database, then creating the extension before use. Project page: @@ -16,7 +16,7 @@ License: PostgreSQL License PostgreSQL's native `SET ROLE` / `SET SESSION AUTHORIZATION` has two security weaknesses: first, after escalating privileges a user can casually run `SET log_statement = 'none'` to turn off logging, or `RESET ROLE` to quietly switch back, leaving no audit trail; second, to let DBAs perform superuser work, they typically must be allowed to log in directly as a superuser account, granting excessive and uncontrolled privileges. -set_user's approach is "don't forbid privilege escalation, but make every escalation fully traceable and non-repudiable." Once deployed, all superuser accounts can be set to NOLOGIN; DBAs log in with ordinary accounts and call set_user_u('postgres') when escalation is needed. Throughout the window from escalation until reset_user() restores the original identity: the role switch is written to the log, log_statement is forcibly set to all so that every SQL statement is persisted, and the log prefix automatically gains an AUDIT tag for easy filtering and alerting; at the same time, every channel that could undermine auditing or escape the identity — ALTER SYSTEM, COPY PROGRAM, SET log_statement, SET ROLE, and the set_config() backdoor — is blocked. Because session_user always remains the real login user, "who did what, when, and under which identity" is plainly visible in the logs. +set_user's approach is "don't forbid privilege escalation, but make every escalation fully traceable and non-repudiable." Once deployed, all superuser accounts can be set to NOLOGIN; DBAs log in with ordinary accounts and call set_user_u('postgres') when escalation is needed. Throughout the window from escalation until reset_user() restores the original identity: the user switch is written to the log, log_statement is forcibly set to all so that every SQL statement is persisted, and the log prefix automatically gains an AUDIT tag for easy filtering and alerting; at the same time, every channel that could undermine auditing or escape the identity — ALTER SYSTEM, COPY PROGRAM, SET log_statement, SET ROLE, and the set_config() backdoor — is blocked. Because session_user always remains the real login user, "who did what, when, and under which identity" is plainly visible in the logs. Implementation-wise, it is a C extension loaded via shared_preload_libraries and relies on three kernel mechanisms: ProcessUtility_hook to intercept dangerous statements, object_access_hook to block function-level backdoors, and transaction commit callbacks to guarantee transactional safety of the switch. Access control uses a dual-gate design — at the SQL layer, GRANT EXECUTE determines who may call the functions, while at the configuration layer, allowlists (superuser_allowlist, etc.) can be hot-adjusted at any time to tighten control. In addition, it provides a token-locked set_user(user, token) to prevent escape when a connection pooler holds connections on behalf of clients, and the irreversible set_session_auth() for permanent privilege drop before handing over a connection. @@ -38,7 +38,7 @@ Superuser accounts (e.g., postgres) should be set to NOLOGIN; DBAs log in with o | set_user.block_alter_system | `on` (default) or `off` | `on` | Block ALTER SYSTEM commands | set_user.block_copy_program | `on` (default) or `off` | `on` | Block COPY PROGRAM commands | set_user.block_log_statement | `on` (default) or `off` | `on` | Block changes to log_statement -| set_user.nosuperuser_target_allowlist | Wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of target roles that set_user() may switch to +| set_user.nosuperuser_target_allowlist | Wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of target users that set_user() may switch to | set_user.superuser_allowlist | Wildcard '*' (default) or string | 'dba1, dba2, +admin_group' | List of users allowed to call set_user_u() for escalation | set_user.superuser_audit_tag | String | 'AUDIT' | Log prefix tag | set_user.exit_on_error | `on` (default) or `off` | `on` | Whether to exit the current session on error