feat(deploy): 补齐凭据加密接入迁移和密钥备份恢复

首次安装默认生成独立加密密钥,更新旧部署保留原配置,通过显式开关进入迁移窗口。增加生产迁移包装入口和完整密钥环备份恢复工具,并说明旧镜像升级、严格迁移、轮换及还原流程。

已通过 Bash 安全环境及密钥备份回归;对应 PowerShell 5.1 和 7 脚本已在 Windows 原生环境各通过 49 项验收。宁波服务器验收尚未执行。
This commit is contained in:
2026-10-12 22:24:16 +08:00
parent f59dd33097
commit 5cff1041a9
15 changed files with 563 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
# API Key 加密部署与旧用户兼容
用户 API Key、平台认证、云凭据和存储通道支持分阶段加密升级。未接入加密配置且数据库从未启用加密的旧部署,可以保持旧脚本和 Compose 不变,只更新兼容镜像;现有用户 Key 和平台 Key 的值均保留,不需要重新生成或重新填写。旧模式暂时沿用原存储方式,不能把它描述为已完成加密。
## 部署行为
| 场景 | 行为 |
| --- | --- |
| 使用新版启动脚本新安装(原来无 `.env`) | Bash/PowerShell 脚本生成独立随机 32 字节 keyring,持久保存;默认关闭旧明文读取 |
| 旧部署仅更新镜像 | 无新增密钥配置时保持旧读写与鉴权;数据库中有密文或加密启用标记时拒绝缺少密钥的启动 |
| 旧部署执行新版脚本 | 默认保留原模式;显式首次接入后新写入加密、历史明文临时可读,重复执行保留 keyring |
| 完成迁移并重启 | 原用户 Key 按哈希鉴权,平台按原凭据执行;永久关闭旧明文读取 |
数据库保存加密启用标记和密钥验证值。一旦启用,加密密钥丢失、部分配置或错误配置都会报错,不会静默降级或用新的随机密钥替换已有密钥。`verify --finalize=true` 将数据库状态置为严格模式,后续重启即使旧环境仍写着兼容开关 `true`,也不会恢复明文读取。
## 私有配置
| 变量 | 要求 |
| --- | --- |
| `CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID` | 当前写入密钥 ID,字母、数字、下划线或连字符,长度 1 至 64,大小写精确匹配 |
| `CONFIG_CREDENTIAL_ENCRYPTION_KEYRING` | 单行 JSON 对象;值为独立随机 32 字节密钥的标准 Base64;脚本保留已有条目 |
| `CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT` | 新安装 `false`;存量接入阶段 `true`;迁移完成后 `false` |
这组密钥与 `CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY`、JWT 和数据库密码独立,不得复用或派生。密钥只存私有 `.env` 或安全配置中心;Linux/macOS 文件为 0600,Windows 使用当前用户私有 DACL。数据库备份和密钥备份分开保管。更换目录、重新生成配置或恢复备份时,应带回对应的原 keyring。
Compose 会把 `.env` 注入新建容器,修改文件不会改变已经运行的进程环境,因此接入、关闭兼容和轮换后都要重建全部相关实例。网关独立使用 `AI_GATEWAY_CREDENTIAL_*` 配置;其 API、Worker 和迁移进程共享同一组网关密钥,不能复用 server-main 的密钥。
## 存量部署接入与迁移
先将所有读取凭据的应用和 Worker 升级到兼容版本,再允许加密写入。只理解明文的旧进程无法读取密文,混部时不能直接启用加密。以下命令要求镜像包含本次配套实现和 `/app/dist/credential-migrate.js`,不适用于此前的旧镜像。
1. 备份 MongoDB、相关 Redis 队列及私有配置,记录当前应用版本;暂停凭据修改并排空或暂停消费者。
2. 显式接入独立密钥,并重建相关服务。Linux/macOS:
```bash
DEPLOY_ENABLE_CREDENTIAL_ENCRYPTION=true ./update.sh
```
Windows PowerShell:
```powershell
$env:DEPLOY_ENABLE_CREDENTIAL_ENCRYPTION = 'true'
.\update.ps1
Remove-Item Env:DEPLOY_ENABLE_CREDENTIAL_ENCRYPTION
```
3. 核对容器内 MongoDB 主机及数据库,按次序执行正式迁移入口。默认 Compose 示例为 `172.21.0.3/aidraw`;自定义连接必须使用实际值。
```bash
bash scripts/migrate-credential-encryption.sh inventory 172.21.0.3/aidraw
bash scripts/migrate-credential-encryption.sh dry-run 172.21.0.3/aidraw
bash scripts/migrate-credential-encryption.sh apply 172.21.0.3/aidraw
bash scripts/migrate-credential-encryption.sh apply 172.21.0.3/aidraw
bash scripts/migrate-credential-encryption.sh verify 172.21.0.3/aidraw
bash scripts/migrate-credential-encryption.sh finalize 172.21.0.3/aidraw
```
PowerShell 使用 `scripts/Migrate-CredentialEncryption.ps1 -Mode inventory -ExpectedTarget '172.21.0.3/aidraw'`,依次替换 Mode 为 `dry-run`、`apply`、`apply`、`verify`、`finalize`。
4. 确认 verify 零待更改、零明文、零冲突;把 `.env` 的 `CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT` 改为 `false`,重建全部相关进程,恢复消费,并用升级前的原用户 Key 验证业务调用。
入口只运行迁移进程,不启动新的应用消费者。正式目标须使用 `--production=true --expected-target=主机/数据库` 精确匹配;工具从服务环境继承认证、数据库名和 Redis 队列配置,不在命令或日志中输出密钥。队列使用配置中的 Redis 主机和 DB,显式覆盖 Redis URL 时还需匹配 `--expected-redis=主机/DB`。
checkpoint 保存在私有 `data/credential-migration` 目录,绑定目标、密钥和轮换设置。旧验收工具的无版本 checkpoint 不能直接复用;换目标或轮换时使用新的 checkpoint。遇到并发冲突会停止,保留 checkpoint、处理冲突后重跑;verify 从头扫描,不会因 checkpoint 跳过残留明文。工具保持 MongoDB 4.4 兼容。
迁移保留用户 Key 的原值、所有权、权限、撤销状态和有效期,增加哈希、密文及摘要。只有哈希的历史 Key 继续鉴权,页面显示无法查看。普通列表不返回完整值;复制或查看由登录会话按需请求。平台凭据按原记录身份加密,编辑摘要会保留原值,复制平台会按新记录身份重新加密。
## 备份、还原与换机
数据库归档只包含数据库中的密文、API Key 哈希、记录身份和 `credential_encryption_state` 验证值,**不会包含部署环境中的解密密钥**。后台的手动/每日自动备份仍可使用,但单独下载数据库文件不足以完成换机恢复。禁止把私有密钥放入上传 OSS 的数据库归档。
需要配套保管:
- 对应时间点的完整 MongoDB 备份,包括主库中的加密状态集合;多库归档包含配置中列出的业务库。
- 完整原 keyring 和 active ID,包括所有仍被历史备份使用的旧密钥。密钥轮换完成不代表旧备份已变为新密钥;保留旧备份期间仍需保留对应旧密钥。
- 原 `CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY` 和 `CONFIG_JWT_SECRET`:已有 MCP、插件、OIDC/JWT 配置使用另一套加密或兼容派生,不能只备份新 API Key 的 keyring。另需私有保存完整 `.env`、相关 `.env.*` 与安全配置中心配置,保留审计、OTP 等原有安全配置和连接参数。
- 需要恢复未完成任务时,同一维护窗口的 Redis 队列备份及相应消费者版本;数据库备份不包含 Redis、PostgreSQL、对象存储或模型文件。
### 独立密钥备份入口
Linux/macOS(输出目录应位于部署目录之外的私有位置):
```bash
mkdir -p "$HOME/.easyai-recovery"
chmod 700 "$HOME/.easyai-recovery"
bash scripts/credential-keys.sh export "$HOME/.easyai-recovery/credential-keys-20261012.env"
```
Windows PowerShell:
```powershell
.\scripts\Manage-CredentialKeys.ps1 -Mode export -Path 'D:\PrivateRecovery\credential-keys-20261012.env'
```
先创建私有输出目录。导出文件保留完整 keyring、兼容开关、配置主密钥和 JWT 原值;文件为 Linux/macOS 0600 或 Windows 当前用户独占 DACL。已有备份不会被覆盖,非法/部分配置和无 keyring 的旧模式不会生成替代密钥。这是**含完整密钥的私有配置文件,不是密码加密的归档**;离机副本放入加密保险库,与数据库归档分开保存。文件及其路径不应提交 Git、发聊天或上传公开存储。
首次接入后立即导出;每次新增或切换密钥后使用新文件再导出。数据库与密钥备份按日期、应用版本和归档指纹配对,并保留旧版本,不能仅留最近一次 active 密钥。旧模式还未生成加密 keyring 时,先保存旧 `.env` 和数据库原备份,接入后补充密钥备份。
### 还原顺序
1. 进入维护窗口,暂停所有写入及消费者,备份当前目标库、队列和私有配置。全库还原使用 `--drop`,会覆盖相应集合,无法与业务写入保证原子性。
2. 在目标目录准备 `.env`,修正地址等连接参数,恢复对应原密钥:
```bash
bash scripts/credential-keys.sh restore "$HOME/.easyai-recovery/credential-keys-20261012.env"
```
```powershell
.\scripts\Manage-CredentialKeys.ps1 -Mode restore -Path 'D:\PrivateRecovery\credential-keys-20261012.env'
```
还原前校验配置,其他环境变量保留;原 `.env` 会保存私有回退副本。恢复入口明确用备份的完整 keyring 替换目标加密配置,适用于目标已误生成另一组随机密钥的情形;其他运行中实例须同步恢复对应配置。旧密钥备份若缺少配置主密钥/JWT 两项,额外恢复原私有 `.env`,不能以新生成值替代。
3. 先重建承接还原的兼容版本服务,让它读取已恢复的环境配置;其他实例和消费者保持暂停,再通过原后台备份记录或上传备份还原。正在运行的进程不会自动读取 `.env` 更改;目标原数据采用另一套密钥而导致服务无法启动时,使用下述离线隔离流程。
4. 后台会先还原至随机命名的临时数据库,检查归档数据库名是否属于已配置业务库,验证全部 API/平台密文、旧 key ID,以及 MCP、插件、OIDC/JWT 加密配置。缺钥、错钥、数据损坏、不安全压缩包或预检失败会拒绝,业务库尚未被覆盖。临时库会保留原索引;加密环境恢复旧明文归档时,先在临时库迁移历史凭据并保持原用户 Key,再导出校验后的归档覆盖业务集合,恢复严格状态。
5. 还原后重建全部相关进程,清理业务缓存;验证原用户 Key 的权限/过期/撤销状态、原平台实际调用、配置查看与复制,再恢复消费。若同时恢复旧 Redis 任务,先运行正式迁移 `apply → verify` 清除历史任务载荷凭据;`verify/finalize` 通过后再重建和消费。
预检需要 Mongo 账户在临时数据库中创建、读写和删除数据的权限,以及临时磁盘空间;权限或空间不足会失败,不会绕过预检。临时库位于同一 Mongo 实例,需约一份数据库工作空间和一份归档空间。macOS 归档附带的 `._*` / `__MACOSX` 资源元数据在解压时忽略。正常完成/失败会清理临时库与校验归档;进程被强制终止时,可能留下 `easyai_credential_restore_*` 临时库,运维确认归属后清理。线上还原仅支持归档内原库名与当前配置匹配;换库名、目标无法启动或跨环境恢复优先走离线隔离流程。
### 无法启动或离线换库恢复
保持业务和消费者停止。在隔离 Mongo 实例/临时目标中用原密钥还原归档,完整保留 `_id`、API Key 哈希和加密状态集合;换库使用 MongoDB Tools 的 `--nsInclude`、`--nsFrom` / `--nsTo` 映射,先核对目标再执行,语法参照 [MongoDB Tools 官方说明](https://www.mongodb.com/docs/database-tools/mongorestore/mongorestore-examples/)。新 API/平台凭据的 AAD 绑定集合、记录身份和字段,未绑定数据库名称;同库结构的换库恢复可保留原值,不能重生成记录 ID 后直接搬密文。
如果归档早于加密接入,在隔离库使用相同原 keyring 和临时兼容开关 `true` 运行正式迁移,再 `verify/finalize`,制作新的已加密备份后恢复目标。`mongorestore --drop` 只删除归档包含的集合,旧归档没有加密状态集合时,直接覆盖已严格化的目标会留下旧标记而混入明文;不要跳过隔离迁移直接手工覆盖。
配置、密钥、数据库和队列确认对应后启动兼容版本,校验正常再切换业务。遗失任何仍在使用的密钥时,不能通过重置 active ID、设兼容开关或生成随机密钥恢复密文,应找回对应私有备份。
## 轮换与回滚
轮换先加入新密钥并保留旧密钥,再切换 active ID;使用新的 checkpoint 执行 `--rotate=true`,验证业务数据与数据库验证值均已迁移后,运行环境才可移除旧密钥;历史备份仍需保留原密钥。工具会随 `apply --rotate=true` 迁移验证值,并保持严格状态。只切换 active ID 不代表可以删除旧密钥。
首次加密写入后,只能回滚到支持当前密文格式的应用。镜像回滚不会撤销数据库变更,也不能恢复遗失的密钥;更新清单不得声称可无条件回滚到只理解明文的旧应用。恢复数据库和队列备份时,要协调应用版本并处理备份后产生的数据。
## 本地验收
2026-10-12 使用真实 MongoDB 4.4、Redis、PostgreSQL 18、旧发布镜像及本地构建的新镜像验证升级和迁移。用户 Key 与平台凭据在仅更新镜像、接入、迁移、关闭兼容和重启阶段保持原值;文本调用连接真实供应商。Windows PowerShell 5.1 和 pwsh 7.6.5 各 33 项原生扩展检查通过,覆盖密钥独立生成、幂等、非法配置拒绝、私有 DACL 和日志泄密。生产环境未执行迁移或部署。
+2
View File
@@ -1,5 +1,7 @@
# EasyAI Windows 一键部署方案
API Key 加密和未升级脚本用户的兼容路径统一见 [API Key 加密部署与旧用户兼容](API_KEY_ENCRYPTION_DEPLOYMENT.md)。新安装自动生成独立密钥;存量部署默认保持兼容,首次接入后迁移验证并重启,原用户 Key 和平台 Key 保持不变。密钥备份/还原入口为 `scripts/Manage-CredentialKeys.ps1`,数据库归档与原密钥分开保管,按上述说明先还原密钥、重建服务再还原数据。
## 1. 背景与目标
`start.sh` 是 Linux 下的一键部署脚本,本方案为 Windows 平台提供等效的 `start.ps1` PowerShell 脚本。针对 Windows 典型场景做如下**精简**: