回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复: - 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录) - tmp/(32.4 M,按接续棒命名的过程临时区) - .workbuddy/tmp/(39.5 M) - 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物) - tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留 入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与 接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、 .workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。 排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、 打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
140 lines
7.8 KiB
Markdown
140 lines
7.8 KiB
Markdown
# 建库权限 · D 档被证伪后的修正判定(2026-09-23)
|
||
|
||
> **一句话结论**:2026-09-22 拍板的 **D 档(`SECURITY DEFINER` 函数建库)在 PostgreSQL 上不可实现** —— 这是 PG 内核硬限制,不是实现姿势问题。**已回滚,服务器零残留**。修正后可行路径为 **B 档 / C 档**二选一,**等你拍板**。
|
||
> 🔴 **2026-09-23 05:4x 更新:用户已拍板「B」** ⇒ 落地件另见 `交付物/插件建库权限-B档落地-20260923.md`(已在 47 落地 + 11 条验收通过)。本文以下保留为**判定过程与证据记录**。
|
||
> **机位**:仅 **47**(PG 只在 Manager 上;106 无 PG)。
|
||
> **本轮对生产的净影响**:**零**(建了又按回滚流程清掉;`dshs` 权限、内核 13 表、服务状态全部原样)。
|
||
|
||
---
|
||
|
||
## 一 D 档为什么不可实现(实测证据,非推断)
|
||
|
||
PG 对 `CREATE DATABASE` 有**两层独立禁止**,任一层单独就足以否决:
|
||
|
||
| 层 | 报错原文 | 触发条件 |
|
||
|---|---|---|
|
||
| 事务层 | `CREATE DATABASE cannot run inside a transaction block` | 任何事务内 —— PL/pgSQL 函数体**恒在事务内** |
|
||
| 函数层 | **`CREATE DATABASE cannot be executed from a function`** | PG 显式禁止从函数发起(即使 `SECURITY DEFINER`) |
|
||
|
||
实测对照(同一私有 schema、同一 `SECURITY DEFINER` 姿势,只换语句):
|
||
|
||
```
|
||
函数内 CREATE SCHEMA → schema_ok ✅ 成功
|
||
函数内 CREATE ROLE → role_ok ✅ 成功
|
||
函数内 CREATE DATABASE → ERROR: CREATE DATABASE cannot be executed from a function ❌ 内核拒绝
|
||
```
|
||
|
||
⇒ **`SECURITY DEFINER` 这条封装路径对 `CREATE DATABASE` 无效**。当初选 D 档的动机("让平台能建库,但不把 `CREATEDB` 给 `dshs`")在这条路上**无法同时满足**。
|
||
|
||
### 一并探明的旁路(避免重复试)
|
||
|
||
| 旁路 | 结论 | 证据 |
|
||
|---|---|---|
|
||
| `dblink` 扩展 | ❌ 不可用 | `pg_available_extensions` 无该条目;`select dblink_connect(...)` ⇒ `function does not exist` |
|
||
| `postgres_fdw` 扩展 | ❌ 不可用 | 同上,未安装且不可安装 |
|
||
| 已装扩展 | 仅 `plpgsql` | `select extname from pg_extension` ⇒ `plpgsql`(单条) |
|
||
| `su postgres` 到平台进程 | ❌ 不存在该用户 | `su: user dshs does not exist` —— `dshs` 是 **systemd 服务名**,不是系统用户 |
|
||
|
||
---
|
||
|
||
## 二 修正后可行路径(两条,各有优有劣 ⇒ 交你拍板)
|
||
|
||
### 候选 B · 角色级授权(给 `dshs` 加 `CREATEDB`)
|
||
|
||
做法:`ALTER ROLE dshs CREATEDB`,平台侧用现有 `dshs` 连接直接执行 `CREATE DATABASE dshs_pl_<id>`。
|
||
|
||
**优点**
|
||
- 实现最直:平台代码里零新增凭据、零新增连接路径,一条 `Pool.query` 即可。
|
||
- 与现有部署形态完全同构(`DSHS_DB_URL` 不变)⇒ 换机器重建时无额外步骤。
|
||
- 权限面收在 PG 内,`pg_roles` / 审计里可查可审计。
|
||
|
||
**缺点**
|
||
- `dshs` 拿到**全局** `CREATEDB` ⇒ PG 没有"只允许建 `dshs_pl_*` 前缀库"的原生机制,白名单只能靠平台代码自觉。
|
||
- 一旦平台代码被注入,攻击面从"当前库"扩到"可建库"(虽然仍不能 `DROP` 别人的库)。
|
||
- 这正是 09-22 否掉 A 档的**同一条理由**,换个档位名原样复现。
|
||
|
||
### 候选 C · 平台侧超管直连建库
|
||
|
||
做法:平台侧持一份超管凭据(只落 systemd drop-in,600),建库时单开一条连接、单语句执行、用完即断。
|
||
|
||
**优点**
|
||
- `dshs` 角色权限**完全不扩大**(保持 `f|f|f`)—— 保住 09-22 的原始意图。
|
||
- 建库动作天然单点:只有这一处代码能建库,grep 可穷举,审计点唯一。
|
||
- PG 侧不受"函数/事务"限制(已实测单语句 `-c` 建删成功)。
|
||
|
||
**缺点**
|
||
- 超管凭据进入**应用进程环境** ⇒ 凭据面从"运维手上"扩到"平台进程内存里"。
|
||
- 平台进程以 root 运行 ⇒ 凭据泄露的最坏后果是同机提取,风险等级高于 B。
|
||
- 换机器重建时多一条必须同步的凭据项(漏了就建不了库)。
|
||
|
||
---
|
||
|
||
## 三 实测读数总表(2026-09-23 · 全部现场取证)
|
||
|
||
| 项 | 读数 | 含义 |
|
||
|---|---|---|
|
||
| PG 版本 | `PostgreSQL 13.23` | 与既有认知一致 |
|
||
| 已装扩展 | `plpgsql` | 无 `dblink` / `postgres_fdw` |
|
||
| `dshs` 权限 | `rolsuper=f` `rolcreatedb=f` `rolcreaterole=f` | 无间接提权 |
|
||
| `dshs` 在 `dshs` 库 | `has_database_privilege('dshs','dshs','CREATE') = t` | 建 schema **零扩权** |
|
||
| 平台进程用户 | `systemctl show dshs -p User` ⇒ 空 | 以 **root** 运行 |
|
||
| `pg_hba` | `local all all peer` + `host … 127.0.0.1/32 scram-sha-256` | 超管走 socket peer;平台走 TCP 口令 |
|
||
| 超管通路 | `su postgres -c 'cd /tmp && psql -h /var/run/postgresql -p 15432 -d dshs …'` | ✅ 可用(⚠️ 必须先 `cd /tmp`) |
|
||
| `dshs_pl_%` 库数 | **0** | 尚无业务插件库 |
|
||
| 内核表数 | **13** | 与 §2-2 既有认知一致,未被本轮触碰 |
|
||
| `lib/` 内建库代码 | **0 处** `CREATE DATABASE` | 建库面确实从零起 |
|
||
| 服务状态 | `dshs` = `active` | 全程未重启 |
|
||
|
||
---
|
||
|
||
## 四 本轮实际执行与回滚(完整可复现)
|
||
|
||
```
|
||
1 抢全局锁 → ✓ 已持(插件投放与分库线-执行棒②)
|
||
2 落地 D 档 DDL → DDL_RC=0(schema dshs_int + 函数 create_plugin_database 创建成功)
|
||
3 跑 7 条验收 → ①create/②幂等 报 ERROR(事务块)⇒ 判定不可实现
|
||
4 旁路探测 → dblink / postgres_fdw / create role / create schema 逐个取证
|
||
5 回滚 → REVOKE → DROP FUNCTION → DROP SCHEMA
|
||
6 回滚后核查 → dshs_int 残留 0 · 函数残留 0 · dshs 权限 false|false|false
|
||
内核表 13 · 服务 active · 探测残留(probe%)全 0
|
||
```
|
||
|
||
**净影响 = 零。** 服务器状态与本轮开工前逐项一致。
|
||
|
||
---
|
||
|
||
## 五 待你拍板(一项)
|
||
|
||
> ✅ **2026-09-23 05:4x 已拍板 = 「B」**。以下候选原文保留作记录;**落地件 ⇒ `交付物/插件建库权限-B档落地-20260923.md`**。
|
||
|
||
**建库通路选 B 还是 C** —— 两者各有优有劣、客观标准分不出高下,故不替你拍:
|
||
|
||
**候选 B · 角色级授权(给 `dshs` 加 `CREATEDB`)**
|
||
- 优点:实现最直、零新增凭据、与现有部署同构、权限面可在 PG 内审计。
|
||
- 缺点:`dshs` 拿到全局 `CREATEDB`,白名单只能靠代码自觉;这正是 09-22 否掉 A 档的同一条理由。
|
||
|
||
**候选 C · 平台侧超管直连建库**
|
||
- 优点:`dshs` 权限完全不扩大(保住 09-22 原始意图)、建库动作单点可穷举、PG 侧无函数/事务限制。
|
||
- 缺点:超管凭据进入以 root 运行的应用进程 ⇒ 凭据面扩大,最坏后果同机提取。
|
||
|
||
我的倾向:**C** —— 它保住了 09-22 拍 D 档时想守的那条线(角色权限不扩大),而凭据面的代价可以通过"只落 600 drop-in + 单点调用 + 全量审计"压到与 B 同级;B 则是把当初明确否掉的形态原样装回来。此倾向可推翻。
|
||
|
||
---
|
||
|
||
## 六 拍板后要落的文档(⛔ 本轮未动,等拍板)
|
||
|
||
1. `DEPLOY-本部署.md` DB 初始化步骤 —— 按选定档位写建库通路。
|
||
2. `交接单/插件投放与分库线-①…md §五 S4-2` + `§十-D1` —— 建库 = **admin 显式按钮**(原文写"上传钩子建库",须改)。
|
||
3. `DB-03-插件数据面规范.md §三 / §四 / §六` —— 谁建库 / 何时建 / 台账表 `plugin_datastores`。
|
||
4. 平台侧错误码映射同步改:D 档的 `PLUGIN_DB_HELPER_MISSING` 随档位作废。
|
||
|
||
---
|
||
|
||
## 七 未做(具名原因)
|
||
|
||
| 项 | 原因 |
|
||
|---|---|
|
||
| B / C 档的实际落地 | 依赖你拍板(§五)⇒ ⛔ 不自选 |
|
||
| 上述 §六 四份文档回改 | 内容随档位而变 ⇒ 拍板前落笔必返工 |
|
||
| `drop_plugin_database`(彻底卸载用) | ⛔ 不在拍板范围(`DB-03 §六-4` 将来一并走) |
|