Files
dsh_shenxian/dsh-server-docs/04-调整方案/140-涉密内容外置到配置目录.md
T
admin 452924d89c feat(config): 涉密内容外置到配置目录(档案 140)
把散落在代码里的真实部署值统一收进 config/,代码改为引用配置,
使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。

新增 config/:platform.env.example(模板)· load.sh(shell 加载器)·
index.cjs(node 加载器)· README.md(键一览与优先级)。
真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。

TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用):
platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。
config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由
DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径,
src/** 注释中性化 116 行/53 文件。

scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径
一律改从配置取;web/wake.html 的注册域白名单改为运行时从
location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737
保留值,并把「内置种子必须为空」固化为回归断言。

取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有);
全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归
(/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
2026-09-19 15:12:19 +08:00

153 lines
10 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.
# 140-涉密内容外置到配置目录(2026-09-19 落地)
> **一句话**:把散落在代码里的**真实部署值**(域名 / IP / 主机号 / 内网路径 / Worker 令牌 / PG 口令)
> 统一收进 `config/`,代码改为引用配置;`src/`+`web/`+`test/` **功能性真实标识 = 0**。
## 背景与动机
**用户原话**:「很多涉密内容包含在代码中 开源时非常容易泄密,把所有涉密内容统一整理到配置文件夹对应文件中,
代码中引用对应配置信息」;收口时追加:「改造后记得**完整检查两遍**」。
改造前依赖**导出层脱敏**(`_build_export.py` 的 71 条 `GLOBAL` + 73 条 `REGEX_RULES` + 75 条 `LEAK_PROBES`)
把生产值替换成占位符 —— 那是**事后擦除**,规则漏一条就泄一条(导出技能事故 #17 就是这么来的:
已公开仓库里躺着 7 个含内网主机号与 PG 口令的脚本)。本次改为**事前分离**:真实值根本不在代码里。
## 用户决策
| 事项 | 决定 |
|---|---|
| 真实值放哪 | 新建 `config/` 目录,真实值进 `config/platform.env`(**.gitignore 排除、不进开源导出**) |
| 代码怎么取 | shell 走 `config/load.sh`;node 脚本走 `config/index.cjs`;TS 走 `src/platform-paths.ts` |
| 缺配置时的行为 | **中性默认值**(不含任何真实路径/域名)+ shell 侧发告警;⛔ 不把生产值留作代码默认 |
| 注释里的真标识 | **中性化**(`<server-public-ip>` / `<base-domain>` / `<host-a>`),注释无法「引用配置」 |
| 测试夹具 | 改 RFC 2606/5737 保留值(`example.net` / `203.0.113.10` / `w-1`·`w-2`) |
## 实现
### A. 新增配置层(5 文件)
| 文件 | 入库 | 作用 |
|---|---|---|
| `config/platform.env.example` | ✅ | 模板(全占位符) |
| `config/platform.env` | ⛔ gitignore | 本机真实值;服务器侧由其自身 systemd env 生成(600) |
| `config/load.sh` | ✅ | shell 加载器:**系统 env > platform.env > 中性默认**;派生 `DSH_STATE_DIR`/`_BACKUP_DIR`/`_ARTIFACT_DIR` |
| `config/index.cjs` | ✅ | node 脚本加载器(零副作用,同优先级口径) |
| `config/README.md` | ✅ | 键一览 + 用法 + 优先级 |
`.gitignore` 增补:`config/platform.env` · `config/*.env.local`。
### B. TS 侧:单一来源 + 去生产值
- **新增 `src/platform-paths.ts`** —— 部署相关路径的**唯一解析处**(零副作用,不建目录/不写文件):
`dataRootDir/platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath`
⚠️ 刻意**不**调 `resolveConfig()`(它会 mkdir 数据根并可能生成 `secret.key`)。
- `src/config.ts`:新增 `platformDir/stateDir/backupDir/artifactDir/installDir` 字段;
**`DEFAULT_OVERLAY_BOOTSTRAP_SEEDS` 由 `['https://<生产域>/dshs-relay']` 改为 `[]`**。
- `src/net/relay/directory.ts`:**`DEFAULT_OVERLAY_SEED` 由生产 URL 改为 `''`**(`overlayEnvSeeds()` 未配 ⇒ 返回空)。
- 4 处硬编码绝对路径改为引用:`web/home-files.ts`(backups)· `web/server.ts`(state)·
`web/routes/admin.ts`(`scriptPath('ensure-biz-plugins.cjs')` + `stateDir()/runtime-baseline.json`)·
`web/routes/dsh.ts`(capabilities)· `web/routes/overlay-nodes.ts`(`dataRootDir()/overlay/nodes.json`)·
`net/relay/registry.ts`(`DSH_RELAY_LIB_DIR ?? installDir()`)。
- 注释中性化:`src/**` 116 行 / 53 文件(仅注释行,零行为风险)。
### C. `scripts/` 侧(内部运维脚本,36 文件)
- 真凭据 → 配置:`W47_TOKEN`/`W106_TOKEN` → `$DSHS_CLUSTER_AGENT_TOKEN`/`$DSH_PEER_AGENT_TOKEN`;
PG 口令 → `$DSHS_PG_PASSWORD`;隧道目标 → `$DSHS_TUNNEL_TARGET`。
- 路径/地址/主机号 → 配置:`/opt/dshs`→`$DSH_INSTALL_DIR`、`/var/lib/dshs`→`$DSHS_DATA_ROOT`、
`/opt/dsh/*`→`$DSH_(STATE|BACKUP|ARTIFACT)_DIR`、`47.77.182.89`→`$DSH_HOST_PUBLIC_IP`、
`w-47`/`w-106`→`$DSHS_CLUSTER_HOST_ID`/`$DSH_PEER_HOST_ID`。
- 🔴 **两处必须手改的形态**(脚本守卫刻意跳过):
① **引号定界 heredoc**(`<<'NFTEOF'` / `<<'ENVEOF'`)内变量**不展开** ⇒ 替换会产出字面量 `"$VAR"`:
`install-egress-guard.sh` 改用 `__HOST_ADDRS__` 占位符 + 写完文件后 `sed` 注入;
`switch-B2-dropin.sh` 的 heredoc 是**非引号**定界(可展开)⇒ 直接参数化。
② **单引号内的 ssh 载荷**(`ssh h 'mkdir -p /var/lib/dshs'`)⇒ 改双引号本地展开后下发。
- `scripts/dshlog.mjs` 的主机表(含真 IP 与 ssh 别名)改为读 `DSHLOG_HOSTS`(JSON)。
### D. `web/` 与 `test/` 侧
- `web/wake.html` 的 `safeNext()`:**旧的 `*.ai1net.com` 白名单正则**改为运行时从
`location.hostname` 推导注册域(去最左一段)⇒ 换域名零改动(导出技能 §3「特例」的既定口径)。
- `test/**` 夹具 **119 行 / 13 文件**:生产域名 → `example.net`/`example.org`(RFC 2606)、
真 IP → RFC 5737 文档段、`w-47`/`w-106` → `w-1`/`w-2`、`/var/lib/dshs` → `/var/lib/dsh-test`。
- `test/overlay-bootstrap.test.mjs` 的**语义断言**同步更新:
由「内置种子 == 生产 URL」改为「**内置种子必须为空**」(防止生产域名再被写回代码)。
### E. 提交 / 部署
| 项 | 值 |
|---|---|
| 备份 | `/opt/dsh/backups/pre-secrets-config-20260919-150752/lib`(294 文件) |
| 部署 | `config/` → `/opt/dshs/config/`(服务器侧从自身 env 生成 `platform.env`,**32 键 / 600**);`lib/` → `/opt/dshs/lib`(297 文件) |
| 新增 drop-in | `/etc/systemd/system/dshs.service.d/platform-dirs.conf`:`DSH_PLATFORM_DIR=/opt/dsh` · `DSH_INSTALL_DIR=/opt/dshs` |
| 重启 | `systemctl restart dshs`(开发环境,无需先知会) |
## 验证记录
| 项 | 结果 |
|---|---|
| `tsc -p tsconfig.json --noEmit` | **exit 0** |
| `npm test`(375 用例) | **373 pass / 1 fail / 1 skip**;唯一失败 = `lease: 释放后归零…`,**既有失败**(重建 lib 前的旧产物上就是同一处) |
| 全部改动 `.sh` | `bash -n` **全通过** |
| 全部改动 node 脚本 | `node --check` **全通过** |
| 全仓扫描(大小写不敏感) | `src/`+`test/`+`scripts/`+`web/`+`assets/`+`config/` **= 0 命中** |
| 部署后派生路径 | `/opt/dsh/state`、`/opt/dsh/backups` 仍在原处;**`/var/lib/dshs/platform` 未被误建**(零回归判据) |
| `capabilities.json` / `runtime-baseline.json` / `ensure-biz-plugins.cjs` / `overlay nodes.json` | 均仍在原路径 ✅ |
| 线上端点 | `portal.html`/`login.html`/`admin.html`/`favicon.svg` **200**;`/api/admin/users` 未认证 **401** |
| 单元 | 47 `dshs`/`dshs-pg`/`dshs-worker` **active**;近 3 分钟 `ENOENT|Cannot find module|TypeError` **0** |
## 事故 / 踩坑记录
### 🔴 ① 我用 `git stash` 做基线比对,被 SIGTERM 打断 ⇒ **`.git/refs` 被删、仓库不可识别**
- **现象**:`git rev-parse` 报 `not a git repository`;`.git/refs` 目录**不存在**,`packed-refs` 也没有。
- **取证**:`.git/objects/pack/*.pack` **完好**(`verify-pack` 正常列出提交);**三处 reflog**
(`logs/HEAD`、`logs/refs/heads/master`、`logs/refs/remotes/origin/master`)末行**全部收敛于
`9c2e7975aca484463afd5f2a36e5484222f040c0`**,且与本轮开机时实测的本地 HEAD / `origin/master` 一致。
- **恢复**:① 先 `tar czf` 保全工作树(2.6 MB);② 由 reflog 重建 `refs/heads/master` 与
`refs/remotes/origin/master`;③ `git fetch origin` 取回缺失对象(pack 里缺最新 commit);
④ `git reset`(mixed,不碰工作树)重建 index;⑤ `git fsck` **干净**、77 项改动全部正常可见。
- **教训(已固化)**:**⛔ 不要用 `git stash` 做「临时回到基线」** —— 它是写操作且不可中断;
要基线比对就用 `git worktree add` 或在导出副本里 checkout。
**先落 patch 备份**(本次 `/tmp/mysrc.patch` 与工作树 tar 包救了场)。
### ② 我的「注释中性化」正则曾把 ASCII 标点吃进去(本轮未发生,但踩到了它的同类)
清理规则只准碰**全角标点**(本轮实现里已限定「只处理整行注释」,规避了该类事故)。
### ③ 「字符串内被打入」两处 **由测试抓出**
`verify-cluster-domain.mjs` / `verify-platform-admin-section.mjs` 里 `'test.ai1net.com'` 这类
**引号内字面量**被映射规则打进 `cfg.get(...)` ⇒ JS 语法错。
⇒ **教训**:映射不能只按「行」判断,必须按**字面量是否在字符串/引号内**判断;
**改完必须逐文件 `node --check`**(两处就是靠它抓到的)。
### ④ 大小写敏感漏扫
`register-guard.test.mjs` 里的 `'https://AI1net.com/'`(大写 `AI`)逃过第一轮扫描,
**由测试断言失败暴露**。⇒ 扫描一律 `re.I`。
### ⑤ 内置种子不是「可清空的常量」而是**测试夹具的依赖**
清空 `DEFAULT_OVERLAY_SEED` 后,3 个用例跟着红(`B1`/`B7`/`S0`)——
它们把该常量当**合法公网 URL 夹具**用。修法 = 让夹具自带 `FIXTURE_SEED`,
**断言改为「常量必须为空」**(把「不留生产域名」变成回归项)。
## 回滚 / 注意
```bash
# 代码回滚:删 config/ 改动即可(纯新增目录 + 引用改造),或
git -C /d/github/dsh_shenxian checkout -- src/ scripts/ web/ test/
# 服务器回滚:
rm /etc/systemd/system/dshs.service.d/platform-dirs.conf && systemctl daemon-reload
cp -a /opt/dsh/backups/pre-secrets-config-20260919-150752/lib /opt/dshs/lib
systemctl restart dshs
# config/ 目录留着无害(不被旧代码引用)
```
- ⚠️ **`config/platform.env` 是新的「单一秘密副本」** ⇒ 必须保持 `600`、保持 gitignore;
**开源导出层必须显式排除 `config/platform.env`**(导出白名单是 `INCLUDE_DIRS`,`config/` 目前不在其中;
若要随包发模板,只能加 `platform.env.example`)。
- ⚠️ 服务器侧 `platform.env` 由 `systemctl show dshs -p Environment` 生成 ⇒
**drop-in 改动后要重新生成**,否则脚本读到旧值。
- 🔴 **drop-in 里必须保留 `DSH_PLATFORM_DIR=/opt/dsh`** —— 删掉它,`platformDir` 会退化成
`<dataRoot>/platform` = `/var/lib/dshs/platform`,于是 state/backups/artifacts **静默搬家**。
- ⏳ 遗留:`test/lease.test.mjs` 的那 1 个失败属**既有**问题,与本次无关,未修。