Skip to content

Commit 41ea367

Browse files
fylornclaude
andcommitted
docs(zh-CN): translate secret-rotation guide
The secret-rotation doc was declared en-only in _meta.ts; the docs page showed an "EN" fallback chip on zh-CN. Add the Chinese translation and flip the locales array to ["en", "zh-CN"] so the chip disappears. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2d6329f commit 41ea367

2 files changed

Lines changed: 166 additions & 1 deletion

File tree

‎src/content/docs/_meta.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const docsOrder: DocMeta[] = [
6262
{
6363
slug: "secret-rotation",
6464
label: { en: "Secret Rotation", "zh-CN": "密钥轮换" },
65-
locales: ["en"],
65+
locales: ["en", "zh-CN"],
6666
summary: {
6767
en: "Rotating provider keys, JWT secrets, and admin credentials in production.",
6868
"zh-CN": "在生产环境中轮换 Provider 密钥、JWT secret 和管理员凭据。",
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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

Comments
 (0)