文件
hyb-oa/.trellis/tasks/08-01-production-secret-recovery-env-externalization/design.md
T

5.8 KiB
原始文件 Blame 文件历史

生产密钥恢复与环境文件外置:设计

1. 设计目标

本任务解决两个相互关联但必须分阶段交付的问题:

  1. 主机或环境文件丢失时,人知道每个配置键如何恢复、代价是什么、由谁完成。
  2. 生产环境文件不再位于源码同步树内,任何 rsync --delete 都无法物理触及它。

Phase 1 只建立恢复合同;Phase 2 才改变生产运行路径。数据库/附件备份继续保持现有边界,不吸收明文 Secret。

2. Phase 1:机器可校验的恢复分类

2.1 分类清单

新增一个位于 deploy/trialrun/ 的 JSON 合同文件,按类别保存 .env.example 键名数组:

{
  "version": 1,
  "categories": {
    "template_rebuild": [],
    "external_reissue": [],
    "direct_rotate": [],
    "downtime_reset": [],
    "preserve_original": []
  }
}
  • 文件只允许大写环境键名和固定类别,不含值、默认值副本、长度、哈希、账号或外部平台地址。
  • validate-env-contract.js 解析 .env.example 和分类文件,断言全集相等、无重复、无未知类别、无多余键。
  • 普通非敏感配置也必须进入 template_rebuild,避免清单只覆盖看起来像 Secret 的少数键。
  • FIELD_ENCRYPTION_KEY 固定归入 preserve_original;POSTGRES_PASSWORD/DATABASE_URL 的关系在恢复卡中说明,数据库角色口令本身归入 downtime_reset。

2.2 恢复卡

更新现有 deploy/trialrun/runbook.md 紧急恢复卡,不新建第二份 runbook:

  • 每类给出丢失影响、责任角色、恢复来源、停止条件和恢复后验证。
  • 数据库流程只描述受控“重置”:停止应用写入与 DB 容器、备份认证配置、用无公网暴露的隔离启动临时调整认证、修改角色口令、恢复原认证配置、重启并执行 migration status/ready/runtime smoke。任何一步失败都保持入口关闭。
  • preserve_original 明确原值丢失会导致既有密文永久不可读;不得用新随机值冒充恢复成功。
  • 共享密码库由系统管理员和公司负责人双人可取。仓库只记录角色与季度取用演练结果,不记录产品地址、账号或恢复码。

2.3 Phase 1 验证

  • 单元测试覆盖完整分类、重复键、未知键、漏键、非法类别和分类文件出现疑似值。
  • 环境合同继续检查 config/example/Compose;分类变化纳入同一个失败出口。
  • 敏感扫描证明新增文件只有键名与类别。
  • Phase 1 不通过 SSH、数据库、Compose 或 systemd 访问生产。

3. Phase 2:生产环境文件外置

3.1 运行合同

  • REMOTE_ENV_FILE 成为唯一运行事实源,必须是同步树外的安全绝对路径、普通文件、root 所有、0600。
  • Compose 的四个 env_file 从字面量 ./.env 改为必须显式提供的 ${REMOTE_ENV_FILE};同一变量也继续传给 Compose --env-file 负责插值。
  • deploy.sh、deploy-existing.sh、远端 preflight、自动备份/WARCH3 安装器、备份 runner、巡检和收口脚本统一接收该变量,不保留 ${REMOTE_DIR}/deploy/trialrun/.env 的生产默认值。
  • 本地开发可显式使用仓库外的测试 env;validator 的否定合同针对生产 Compose/部署默认与路径嵌套,不因为开发者工作区存在 gitignored 测试文件而误报。

3.2 负向防护

  • deploy.sh 在 provenance、mkdir、rsync 之前拒绝 REMOTE_ENV_FILE == REMOTE_DIR 或任一方向嵌套。
  • preflight-remote.sh 验证 env owner/mode/非 symlink,并确认路径不在 REMOTE_DIR 下。
  • validate-env-contract.js 拒绝 Compose 再出现 env_file: ./.env、部署脚本重新默认到同步树或 runbook 把生产事实源写回仓库目录。
  • rsync 的 .env exclude 可以作为纵深防御保留,但不再承担安全正确性。

3.3 受控迁移状态机

  1. Prepare:只读冻结活动 Compose、env 路径、4 个 service、PGDATA/storage 和构建身份;检查共享密钥封存完成状态,不读取值。
  2. Copy:在主机上以 root 原子复制当前 env 到外置临时文件,核对键集合、owner/mode/非 symlink 后原子改名;不在日志输出 diff 或值。
  3. Switch:重装 4 个 units,使用新 env 完成 Compose config、新 init 配置预检并切换应用。
  4. Verify:migration status、/ready、external/runtime smoke、普通登录、企微登录/入口、automatic backup daily/watchdog 和 timer 身份均通过。
  5. Retire:在同一批准窗口内按精确旧路径删除旧副本;随后验证旧路径不存在、容器与 units 只引用新路径。

任何 Verify 失败都在旧副本尚存时切回旧合同并重新安装 units;禁止在验证完成前删除旧副本。Retire 后若需回滚,只能使用外置事实源,不能把 Secret 搬回同步树。

4. 密钥版本化的延期边界

当前 v1:<iv>:<tag>:<ciphertext> 没有 key id,单一 FIELD_ENCRYPTION_KEY 无法支持安全轮换。本任务只保证原值可恢复,不修改加密服务、数据库密文或敏感字段 API。

后续独立安全任务至少需要:新格式版本与 key id、keyring 配置合同、当前写入键、旧键只读、惰性与批量重加密、覆盖银行账号/身份证号/通知渠道密文的枚举与计数、轮换回滚和旧键删除门禁。

5. 风险与回滚

  • 最大风险是两份 env 共存期间扩大 Secret 暴露面,因此窗口必须短、两份均 root-only,完成后有否定删除验证。
  • Compose 插值与 service env_file 是两个用途,必须同时验证,不能只改命令行 --env-file。
  • 数据库口令重置步骤属于事故恢复,不在常规部署或自动测试中执行;文档不得弱化停机和隔离要求。
  • 具体密码管理器和成员授权是仓库外 EXTERNAL_GATED 动作,缺少双人取用确认时 Phase 2 不得开始。