# 生产密钥恢复与环境文件外置:设计 ## 1. 设计目标 本任务解决两个相互关联但必须分阶段交付的问题: 1. 主机或环境文件丢失时,人知道每个配置键如何恢复、代价是什么、由谁完成。 2. 生产环境文件不再位于源码同步树内,任何 `rsync --delete` 都无法物理触及它。 Phase 1 只建立恢复合同;Phase 2 才改变生产运行路径。数据库/附件备份继续保持现有边界,不吸收明文 Secret。 ## 2. Phase 1:机器可校验的恢复分类 ### 2.1 分类清单 新增一个位于 `deploy/trialrun/` 的 JSON 合同文件,按类别保存 `.env.example` 键名数组: ```json { "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:::` 没有 key id,单一 `FIELD_ENCRYPTION_KEY` 无法支持安全轮换。本任务只保证原值可恢复,不修改加密服务、数据库密文或敏感字段 API。 后续独立安全任务至少需要:新格式版本与 key id、keyring 配置合同、当前写入键、旧键只读、惰性与批量重加密、覆盖银行账号/身份证号/通知渠道密文的枚举与计数、轮换回滚和旧键删除门禁。 ## 5. 风险与回滚 - 最大风险是两份 env 共存期间扩大 Secret 暴露面,因此窗口必须短、两份均 root-only,完成后有否定删除验证。 - Compose 插值与 service `env_file` 是两个用途,必须同时验证,不能只改命令行 `--env-file`。 - 数据库口令重置步骤属于事故恢复,不在常规部署或自动测试中执行;文档不得弱化停机和隔离要求。 - 具体密码管理器和成员授权是仓库外 EXTERNAL_GATED 动作,缺少双人取用确认时 Phase 2 不得开始。