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

10 KiB
Raw Blame History

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

事故 / 踩坑记录

🔴 ① 我用 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, 断言改为「常量必须为空」(把「不留生产域名」变成回归项)。

回滚 / 注意

# 代码回滚:删 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 个失败属既有问题,与本次无关,未修。