|
| 1 | +# 密钥轮换指南 |
| 2 | + |
| 3 | +本文档介绍 ThinkWatch 使用的各类密钥的轮换流程。 |
| 4 | + |
| 5 | +## JWT Secret 轮换 |
| 6 | + |
| 7 | +JWT Secret(`JWT_SECRET`)用于签发和校验访问令牌 / 刷新令牌。 |
| 8 | + |
| 9 | +### 操作步骤 |
| 10 | + |
| 11 | +1. **生成新 Secret**(至少 32 字符): |
| 12 | + ```bash |
| 13 | + openssl rand -hex 32 |
| 14 | + ``` |
| 15 | + |
| 16 | +2. **安排停机窗口** —— 轮换 JWT Secret 会立即使所有现有令牌失效。 |
| 17 | + |
| 18 | +3. **更新部署中的环境变量**(`.env.production`、Kubernetes Secret 等): |
| 19 | + ``` |
| 20 | + JWT_SECRET=<new-secret> |
| 21 | + ``` |
| 22 | + |
| 23 | +4. **同时重启所有 ThinkWatch 服务器实例**。 |
| 24 | + |
| 25 | +5. **用户需要重新登录** —— 现有的访问令牌和刷新令牌都将失效。 |
| 26 | + |
| 27 | +### 影响 |
| 28 | + |
| 29 | +- 所有活跃会话被终止 |
| 30 | +- 使用 JWT 令牌的 API 消费方必须重新认证 |
| 31 | +- API 密钥(Bearer `tw-*`)**不受影响**(使用基于哈希的认证) |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## 加密密钥轮换 |
| 36 | + |
| 37 | +加密密钥(`ENCRYPTION_KEY`)用于加密存储在数据库中的 Provider API 密钥与 MCP 服务器认证机密(AES-256-GCM)。 |
| 38 | + |
| 39 | +### 操作步骤 |
| 40 | + |
| 41 | +1. **生成新的 32 字节密钥**(64 位 hex 字符): |
| 42 | + ```bash |
| 43 | + openssl rand -hex 32 |
| 44 | + ``` |
| 45 | + |
| 46 | +2. **重新加密所有 Provider API 密钥与 MCP 机密**: |
| 47 | + ```sql |
| 48 | + -- 必须通过程序完成。 |
| 49 | + -- 导出密文 → 用旧 key 解密 → 用新 key 加密 → 写回数据库。 |
| 50 | + ``` |
| 51 | + |
| 52 | + 推荐通过迁移脚本完成: |
| 53 | + ```rust |
| 54 | + let old_key = parse_encryption_key(&old_hex)?; |
| 55 | + let new_key = parse_encryption_key(&new_hex)?; |
| 56 | + |
| 57 | + // 对每个 provider |
| 58 | + let decrypted = decrypt(&provider.api_key_encrypted, &old_key)?; |
| 59 | + let re_encrypted = encrypt(&decrypted, &new_key)?; |
| 60 | + // UPDATE providers SET api_key_encrypted = $1 WHERE id = $2 |
| 61 | + ``` |
| 62 | + |
| 63 | +3. **更新环境变量**: |
| 64 | + ``` |
| 65 | + ENCRYPTION_KEY=<new-64-hex-chars> |
| 66 | + ``` |
| 67 | + |
| 68 | +4. **重启所有实例**。 |
| 69 | + |
| 70 | +### 影响 |
| 71 | + |
| 72 | +- 如果忘记重新加密现有机密,它们将变得不可读 |
| 73 | +- 新建的 Provider / MCP 机密会使用新 key |
| 74 | +- 部署前务必先用新 key 验证一次解密 |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## API 密钥轮换 |
| 79 | + |
| 80 | +API 密钥可通过内置的轮换 API 实现零停机轮换。 |
| 81 | + |
| 82 | +### 通过 API |
| 83 | + |
| 84 | +```bash |
| 85 | +# 轮换密钥(旧密钥在宽限期内仍然有效) |
| 86 | +curl -X POST /api/keys/{key_id}/rotate \ |
| 87 | + -H "Authorization: Bearer <access_token>" \ |
| 88 | + -H "X-Signature-Timestamp: ..." \ |
| 89 | + -H "X-Signature-Nonce: ..." \ |
| 90 | + -H "X-Signature: hmac-sha256:..." |
| 91 | + |
| 92 | +# 响应包含新的明文密钥 |
| 93 | +{ |
| 94 | + "id": "new-key-uuid", |
| 95 | + "key": "tw-...", |
| 96 | + "name": "My Key (rotated)", |
| 97 | + "key_prefix": "tw-..." |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +### 通过 Web UI |
| 102 | + |
| 103 | +1. 进入 **AI Gateway → API Keys** |
| 104 | +2. 在目标密钥行点击 **Rotate** 按钮 |
| 105 | +3. 在弹窗中确认轮换 |
| 106 | +4. 立即复制新密钥(仅显示一次) |
| 107 | +5. 用新密钥更新你的应用 |
| 108 | +6. 旧密钥在配置的宽限期内继续有效(默认:24 小时) |
| 109 | + |
| 110 | +### 自动轮换 |
| 111 | + |
| 112 | +在 **Settings → API Key Policies** 中配置自动轮换: |
| 113 | + |
| 114 | +- **Rotation Period (days)**:设置为 > 0 即启用自动轮换 |
| 115 | +- **Grace Period (hours)**:轮换后旧密钥继续有效的时长 |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## OIDC Client Secret 轮换 |
| 120 | + |
| 121 | +OIDC Client Secret 用于与你的身份提供商(如 Zitadel)建立认证。 |
| 122 | + |
| 123 | +### 操作步骤 |
| 124 | + |
| 125 | +1. **在 OIDC Provider 的管理控制台中生成新的 Client Secret**。 |
| 126 | + |
| 127 | +2. **更新环境变量**: |
| 128 | + ``` |
| 129 | + OIDC_CLIENT_SECRET=<new-secret> |
| 130 | + ``` |
| 131 | + |
| 132 | +3. **重启所有 ThinkWatch 实例** —— OIDC 发现在启动时执行。 |
| 133 | + |
| 134 | +### 影响 |
| 135 | + |
| 136 | +- 重启期间 SSO 登录可能短暂失败 |
| 137 | +- 现有 JWT 令牌在过期前仍然有效 |
| 138 | +- 已建立会话的用户不受影响 |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +### Redis Signing Keys |
| 143 | + |
| 144 | +Signing Keys 用于对状态变更类请求(POST、PUT、PATCH、DELETE)做 HMAC-SHA256 签名校验。其特性: |
| 145 | + |
| 146 | +- 登录与令牌刷新时自动生成(32 字节随机值,hex 编码) |
| 147 | +- 存储于 Redis,TTL 为 24 小时(与刷新令牌一致) |
| 148 | +- **绑定客户端 IP** —— 登录时的 IP 与 key 一同存储;来自不同 IP 的请求会被拒绝 |
| 149 | +- 通过 httpOnly Cookie(`signing_key`)下发到客户端 |
| 150 | + |
| 151 | +**轮换:** Signing Keys 在每次登录和令牌刷新时自动轮换。如需强制为某个用户立即轮换,可在管理后台执行「强制登出」,该操作会使其所有会话与 Signing Key 失效。 |
| 152 | + |
| 153 | +无需手动轮换。 |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +## 最佳实践 |
| 158 | + |
| 159 | +1. **尽量在业务低峰期执行轮换** |
| 160 | +2. **先在预发环境测试**,再轮换生产密钥 |
| 161 | +3. **在运维日志中记录轮换操作** |
| 162 | +4. **轮换后监控异常** —— 检查 `/api/health` 与应用日志 |
| 163 | +5. **不同环境使用不同的密钥**(dev / staging / production) |
| 164 | +6. **将密钥存放在 Vault 中**(HashiCorp Vault、AWS Secrets Manager 等),而非明文文件 |
| 165 | +7. **日志转发器凭证** —— 如果使用带认证的 Kafka、HTTP Webhook 或 Syslog,在 Admin > Log Forwarding 中轮换这些凭证,并在轮换后测试连通性 |
0 commit comments