Files
dsh_ai1net_server/交付物/插件建库权限-D档落地Runbook-20260922.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 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)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

143 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# D 档落地 Runbook · 插件建库权限(`SECURITY DEFINER` 函数 + `GRANT EXECUTE`)
> 🔴 **2026-09-23 状态:本件已作废,⛔ 不得执行。**
> **原因**:2026-09-23 实测证伪 —— PG 显式禁止从函数内执行 `CREATE DATABASE`(`ERROR: CREATE DATABASE cannot be executed from a function`),`SECURITY DEFINER` 封装路径对建库无效。
> **替代件**:`交付物/建库权限-D档被证伪与修正判定-20260923.md`(含实测证据 + B / C 两候选,待拍板)。
> **正文以下保留作过程记录,⛔ 不再作为执行依据。**
>
> **决策**:2026-09-22 19:3x 用户拍板 **D**(原话「D」)。
> **目标**:让平台能"点按钮建库",而 **⛔ 不**把 `CREATEDB` 给 `dshs` 角色。
> **执行前置**:`dshs.service` 全局执行锁必须已释放(动服务器属 R9 管辖);本件可**幂等重跑**。
> **机位**:仅 **47**(PG 只在 Manager 上;106 无 PG)。
---
## 一 前置事实(2026-09-22 实测 · 全部现场取证)
| 项 | 读数 | 含义 |
|---|---|---|
| 超级用户通路 | `su postgres -c 'cd /tmp && psql -h /var/run/postgresql -p 15432 -d dshs …'` ⇒ `current_user=postgres` | ✅ **可用**(`local all all peer` + 本机 socket)|⚠️ 必须先 `cd /tmp`,否则 `su` 切目录被拒 |
| `dshs` 角色 | `rolsuper=f` · `rolcreatedb=f` · `rolcreaterole=f` · **角色成员 = none** | 无任何间接提权路径 |
| `pg_hba` | `host all all 127.0.0.1/32 scram-sha-256` | ✅ `dshs` 对**新建的** `dshs_pl_*` 库也能连 |
| 数据目录 / socket | `/var/lib/dshs-pg` · `/var/run/postgresql/.s.PGSQL.15432` | 非容器;`docker exec` 写法一律无效 |
| 既有 `dshs_%` 函数 | **0 条** | 无命名冲突 |
| ⚠️ `public` schema ACL | `{postgres=UC/postgres,=UC/postgres}` | **PUBLIC 在 `public` 上有 CREATE**(PG13 默认)⇒ 🔴 函数**不得**放 `public` |
---
## 二 执行(一次性 · 幂等)
```sql
-- 1) 私有 schema(放 public 有被同库其他角色抢先建同名函数的风险)
CREATE SCHEMA IF NOT EXISTS dshs_int AUTHORIZATION postgres;
REVOKE ALL ON SCHEMA dshs_int FROM PUBLIC;
GRANT USAGE ON SCHEMA dshs_int TO dshs;
-- 2) 建库函数:库名白名单 + SECURITY DEFINER
CREATE OR REPLACE FUNCTION dshs_int.create_plugin_database(p_name text)
RETURNS text
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog -- ⛔ 必须有:防 search_path 劫持
AS $fn$
DECLARE
v_owner constant text := 'dshs';
BEGIN
IF p_name IS NULL OR p_name !~ '^dshs_pl_[a-z][a-z0-9_]{0,40}$' THEN
RAISE EXCEPTION 'invalid plugin database name: %', p_name USING ERRCODE = '22023';
END IF;
IF EXISTS (SELECT 1 FROM pg_database WHERE datname = p_name) THEN
RETURN 'exists';
END IF;
EXECUTE format('CREATE DATABASE %I OWNER %I ENCODING %L TEMPLATE template0',
p_name, v_owner, 'UTF8');
RAISE LOG 'dshs plugin db created: %', p_name;
RETURN 'created';
EXCEPTION
WHEN duplicate_database THEN
RETURN 'exists';
END;
$fn$;
REVOKE ALL ON FUNCTION dshs_int.create_plugin_database(text) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION dshs_int.create_plugin_database(text) TO dshs;
```
**平台侧调用**(`dshs` 连接、`dshs` 库):
```sql
select dshs_int.create_plugin_database('dshs_pl_trpg_kit'); -- → created | exists
```
要点:
- 返回 `created` / `exists` 两态 ⇒ **幂等由函数保证**,平台不必先查 `pg_database`(避免 TOCTOU)。
- `RAISE EXCEPTION … ERRCODE 22023` ⇒ 库名非法(含注入串)**直接抛**,平台按 400 处理。
- `RAISE LOG` 写进 PG 日志(journald 可查);⚠️ 不进 `plugin_data_audit`(那是**内核表**,跨库不可写 —— 平台侧在**调用后**写台账 + 审计)。
- `OWNER = dshs` ⇒ 新库的 owner 是平台角色,后续建表 / `pg_dump` / `DROP` 都不再需要超级用户。
- `TEMPLATE template0` + `ENCODING UTF8` ⇒ 不受 `template1` 里被塞过的对象影响。
---
## 三 验收(7 条 · 逐条可复现)
```
① 建库成功 [dshs] select dshs_int.create_plugin_database('dshs_pl_selftest') → created
② 幂等 [dshs] 再跑同一条 → exists
③ 库与属主 [postgres] select datname, pg_get_userbyid(datdba) from pg_database
where datname='dshs_pl_selftest' → dshs_pl_selftest | dshs
④ 白名单 [dshs] select dshs_int.create_plugin_database('evil') → ERROR 22023
④' 注入串 [dshs] select dshs_int.create_plugin_database('dshs_pl_x; DROP DATABASE dshs') → ERROR 22023
⑤ ⛔ 权限未扩大 [postgres] select rolsuper, rolcreatedb from pg_roles where rolname='dshs' → f | f
⑥ 无旁路 [dshs] create database t → ERROR 42501(权限不足)
⑦ 可连新库 [dshs] psql -h 127.0.0.1 -p 15432 -d dshs_pl_selftest -c 'create schema s' → CREATE SCHEMA
清理 [postgres] DROP DATABASE dshs_pl_selftest (验收产物,⛔ 别留)
```
⑤ 是本档的**核心断言**:`rolcreatedb` 必须**仍是 f** —— 若它变成 t,说明做成了 A 档,立即回滚。
---
## 四 回滚
```sql
REVOKE EXECUTE ON FUNCTION dshs_int.create_plugin_database(text) FROM dshs; -- 只停新库创建
-- 或彻底移除:
DROP FUNCTION dshs_int.create_plugin_database(text);
DROP SCHEMA dshs_int; -- 空 schema 才可删
```
影响面:**已建好的 `dshs_pl_*` 库不受影响**(owner 是 `dshs`,平台照常建表读写);只是新插件建不了库。回滚点 = 平台建库口返回 `PLUGIN_DB_HELPER_MISSING`。
---
## 五 ⚠️ 必须同步落的两处(否则换机器重建时漏掉)
1. **`dsh-server-docs/DEPLOY-本部署.md`** 的 DB 初始化步骤追加本份 DDL(标 🔴「`dshs` 角色**不**给 `CREATEDB`,建库走 `dshs_int.create_plugin_database`」)。
2. **`交接单/插件投放与分库线-①…md §五 S4-2`** 改写:建库 = **admin 显式按钮** → 调本函数;`§十-D1` 同步改(原文写的是"上传钩子建库")。
3. **`DB-03-插件数据面规范.md §三 / §四 / §六`** 回改(谁建库 / 何时建 / 台账表 `plugin_datastores`)。
---
## 六 平台侧代码接口(下一棒照此实现)
```ts
// src/db/plugin-data/datastore.ts
export async function createPluginDatabase(client: PoolClient, dbName: string): Promise<'created' | 'exists'>
// 错误映射(供路由层用):
// ERRCODE 22023 ⇒ 400 invalid_plugin_db_name
// ERRCODE 42501 ⇒ 503 PLUGIN_DB_HELPER_MISSING (函数缺 / 无 EXECUTE 权)
// 42883 undefined_function ⇒ 503 PLUGIN_DB_HELPER_MISSING
```
`.dsh` 侧声明 → 库名的换算(`DB-03 §一`,⛔ 不改):包名去 scope、小写、`-`→`_`,前缀 `dshs_pl_`。
例:`dsh-plugin-mcn-suite` → `dshs_pl_dsh_plugin_mcn_suite`;`@dsh-local/storyforge` → `dshs_pl_storyforge`。
---
## 七 未做(具名原因)
| 项 | 原因 |
|---|---|
| 本 Runbook 的**执行** | `EXEC_LOCK_HELD_BY_IM_LINE_BATON5`(19:33 起占用;动服务器受 R9 管辖)⇒ 本轮只出可照抄脚本,⛔ 未执行 |
| 对称的 `drop_plugin_database`(彻底卸载用) | ⛔ **不在本次拍板范围** ⇒ 不顺手做;登记为后续(`DB-03 §六-4` 的 `DROP DATABASE` 流程将来一并走) |