709 lines
29 KiB
Markdown
709 lines
29 KiB
Markdown
# 服务端内容审核与消息加密改造文档
|
||
|
||
> 文档状态:分阶段实施
|
||
>
|
||
> 更新日期:2026-09-16
|
||
>
|
||
> 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server)
|
||
|
||
## 实施顺序
|
||
|
||
本项目拆成两个相互独立的实施部分,并统一保存在本文档中:
|
||
|
||
1. 第一部分先完成内容审核,独立开发、灰度、验收和稳定观察;本阶段不修改消息存储格式。
|
||
2. 第二部分在内容审核稳定后实施消息加密;该阶段单独立项、单独发布,并对消息表和历史数据进行分批迁移。
|
||
|
||
两部分禁止同批切换。第二部分只接管“内容审核通过后的消息存储”,不得反向修改第一部分已经验收的审核决策。
|
||
|
||
```text
|
||
第一部分:
|
||
客户端 -> TLS -> IM Server -> 内容审核 -> 现有明文存储
|
||
|
||
第二部分:
|
||
客户端 -> TLS -> IM Server -> 内容审核 -> AES-256-GCM -> 密文存储
|
||
```
|
||
|
||
## 第二部分启动门禁
|
||
|
||
消息加密开发必须同时满足:
|
||
|
||
1. 内容审核已全量上线并通过业务验收。
|
||
2. 误拦截率、阿里云耗时、超时降级和熔断策略达到生产要求。
|
||
3. 消息发送、编辑、资料修改和多 Pod 配置同步已经稳定。
|
||
4. IM 数据库全量备份及恢复演练通过。
|
||
5. 历史消息规模、迁移时间和数据库负载完成评估。
|
||
6. 加密双读、密钥轮换和回滚方案已在测试环境演练通过。
|
||
|
||
## 第一部分:服务端内容审核(优先实施)
|
||
|
||
> 文档状态:第一阶段,优先开发
|
||
>
|
||
> 编制日期:2026-09-16
|
||
>
|
||
> 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server)
|
||
>
|
||
> 本阶段不修改消息存储格式,不实施消息加密。
|
||
|
||
### 1. 目标
|
||
|
||
Admin Server 提供内容审核配置和商户策略管理,IM Server 在内容写入前根据策略执行:
|
||
|
||
1. 本地敏感词补充检查。
|
||
2. 阿里云 `TextModerationPlus` 文本审核。
|
||
3. 按风险等级放行、拦截或仅审计。
|
||
4. 记录审核结果、请求耗时和处置结果,不保存审核明文。
|
||
|
||
第一阶段只为 `tenant.code=default` 的上架默认商户开启阿里云审核。其他团队商户默认关闭,不产生阿里云调用和费用。
|
||
|
||
### 2. 不在本阶段实施的内容
|
||
|
||
- 不改造 `im_messages.content` 和 `im_group_messages.content`。
|
||
- 不建设 AES 加解密模块。
|
||
- 不建设密钥管理表、密钥轮换和历史消息迁移。
|
||
- 不做客户端加密或端到端加密。
|
||
- 不改变现有 HTTPS/WSS 传输协议。
|
||
- 图片、视频和文件本体继续走独立的文件审核链路。
|
||
|
||
### 3. 现有实现和差距
|
||
|
||
| 能力 | 现状 | 改造目标 |
|
||
| --- | --- | --- |
|
||
| 阿里云文本审核 | 文档已人工验证,IM Server 无 Go 调用实现 | 实现 `TextModerationPlus` 客户端并接入业务链路 |
|
||
| 审核服务配置 | `im_external_config` 已有 `moderation` 占位 | 补齐阿里云参数、真实连通性检查和发布 |
|
||
| 商户审核策略 | `im_security_policy.sensitive_word_policy` 可保存 JSON | 定义固定结构并提供后台配置 |
|
||
| 本地敏感词 | Admin 已有 CRUD,IM Server 未消费 | 作为可选补充规则接入 IM 运行时 |
|
||
| 配置同步 | 已有 internal config、Kafka 配置事件和缓存失效 | 复用现有机制扩展审核策略 |
|
||
| 消息审核 | 消息直接保存和广播 | 在保存和广播前审核 |
|
||
| 昵称审核 | 无 | 覆盖用户昵称、群昵称和群名称的所有修改入口 |
|
||
|
||
### 4. 总体链路
|
||
|
||
```text
|
||
客户端
|
||
| HTTPS/WSS
|
||
v
|
||
IM Server
|
||
|-- 权限、宵禁、用户状态等现有校验
|
||
|-- 根据tenant读取审核策略
|
||
|-- 提取用户可见文本
|
||
|-- 本地敏感词检查(可选)
|
||
|-- 阿里云TextModerationPlus(可选)
|
||
|-- 映射风险结果和处置动作
|
||
|-- block:返回业务错误,不入库、不广播
|
||
`-- allow/audit_only:按原逻辑入库和广播
|
||
```
|
||
|
||
### 5. 审核场景
|
||
|
||
| 场景 | 阿里云 Service | 审核时机 |
|
||
| --- | --- | --- |
|
||
| 用户昵称 | `nickname_detection_pro` | 更新用户资料前 |
|
||
| 用户签名 | `nickname_detection_pro` | 更新用户资料前 |
|
||
| 群昵称 | `nickname_detection_pro` | 更新群成员资料前 |
|
||
| 群名称 | `nickname_detection_pro` | 创建群或修改群资料前 |
|
||
| 单聊文本 | `chat_detection_pro` | 消息入库及广播前 |
|
||
| 群聊文本 | `chat_detection_pro` | 消息入库及广播前 |
|
||
| 编辑后消息 | `chat_detection_pro` | 新版本入库前 |
|
||
| 群公告 | `chat_detection_pro` | 发布或修改前 |
|
||
|
||
系统消息、WebRTC 信令、已签名的服务端消息和空文本不调用文本审核。
|
||
|
||
### 6. Admin Server 改造
|
||
|
||
#### 6.1 阿里云服务配置
|
||
|
||
复用 `im_external_config` 的 `service=moderation`,只允许平台管理员维护审核策略参数。阿里云账号与连接参数统一复用 `hg_sys_config` 的 `im_third_party` 分组,不在商户审核配置中重复保存:
|
||
|
||
| 配置 | 说明 |
|
||
| --- | --- |
|
||
| `provider` | 固定 `aliyun_green` |
|
||
| `enabled` | 平台总开关 |
|
||
| `config_json.nickname_service` | 默认 `nickname_detection_pro` |
|
||
| `config_json.chat_service` | 默认 `chat_detection_pro` |
|
||
| `config_json.timeout_ms` | 单次请求超时,建议 1000~2000 ms |
|
||
| `config_json.failure_mode` | `allow` 或 `block` |
|
||
|
||
公共配置来源如下:
|
||
|
||
| `hg_sys_config` 键 | 说明 |
|
||
| --- | --- |
|
||
| `im_third_party.aliyun_access_key_id` | 阿里云 AccessKey ID |
|
||
| `im_third_party.aliyun_access_key_secret` | 阿里云 AccessKey Secret |
|
||
| `im_third_party.aliyun_green_region_id` | 内容安全地域,默认 `cn-shanghai` |
|
||
| `im_third_party.aliyun_green_endpoint` | 内容安全端点,默认 `green-cip.cn-shanghai.aliyuncs.com` |
|
||
|
||
`im_external_config.secret_json` 不再保存内容审核密钥;升级时清空历史重复密钥。Admin Server 的内部接口负责把平台总开关、场景服务参数和公共阿里云配置合并后下发给 IM Server。
|
||
|
||
“检查配置”必须真实调用阿里云测试文本,返回成功与否、耗时、RequestID 和脱敏错误,不能只检查字段非空。
|
||
|
||
#### 6.2 商户审核开关与策略
|
||
|
||
`im_tenant_config` 增加两个商户级权威开关,并在“商户管理”的新增/编辑页面维护:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `aliyun_moderation_enabled` | 是否对该商户启用阿里云内容审核 |
|
||
| `local_moderation_enabled` | 是否对该商户启用本地敏感词审核 |
|
||
|
||
两个开关默认关闭;现有 `default` 商户迁移时保持开启。只要任一开关开启,该商户即进入内容审核流程。安全策略不再重复维护审核引擎开关,只维护场景、风险动作、第三方失败策略和拦截提示。
|
||
|
||
复用 `im_security_policy.sensitive_word_policy`,统一 JSON 结构:
|
||
|
||
```json
|
||
{
|
||
"scenes": {
|
||
"user_nickname": true,
|
||
"user_signature": true,
|
||
"group_name": true,
|
||
"group_nickname": true,
|
||
"private_message": true,
|
||
"group_message": true,
|
||
"group_announcement": true
|
||
},
|
||
"risk_actions": {
|
||
"high": "block",
|
||
"medium": "block",
|
||
"low": "audit_only",
|
||
"none": "allow"
|
||
},
|
||
"failure_mode": "allow",
|
||
"block_message": "内容不符合平台规范,请修改后重试"
|
||
}
|
||
```
|
||
|
||
规则:
|
||
|
||
- 审核引擎是否启用以 `im_tenant_config` 的两个商户开关为准。
|
||
- 每个商户可以独立开启阿里云审核、本地审核,或同时开启。
|
||
- 商户管理员不得查看或修改阿里云 AK/SK。
|
||
- 策略发布使用现有 `im.config.event` 通知 IM Server 按商户失效缓存。
|
||
|
||
#### 6.3 本地敏感词
|
||
|
||
- 现有 `im_sensitive_words` 和分组管理作为补充规则保留。
|
||
- 只有商户配置 `local_moderation_enabled=1` 时才加载。
|
||
- 敏感词和分组按商户隔离,平台管理员及当前商户管理员按权限维护。
|
||
- 词库更新继续发布 `sensitive_words.updated` 事件。
|
||
|
||
#### 6.4 内部接口
|
||
|
||
| 方法 | 路径 | 用途 |
|
||
| --- | --- | --- |
|
||
| GET | `/internal/im-config/moderation/provider` | 拉取阿里云审核服务配置 |
|
||
| GET | `/internal/im-config/tenants/{code}/moderation-policy` | 拉取商户审核策略 |
|
||
| GET | `/internal/im-config/tenants/{code}/sensitive-words` | 拉取商户有效敏感词 |
|
||
|
||
接口复用现有内部服务签名鉴权、时间窗、RequestID 和重放校验,禁止浏览器直接调用。
|
||
|
||
### 7. IM Server 改造
|
||
|
||
#### 7.1 内部模块
|
||
|
||
```text
|
||
server/moderation/
|
||
provider.go 审核Provider接口
|
||
policy.go 商户策略和缓存
|
||
extractor.go 文本提取
|
||
decision.go 风险结果映射
|
||
local/words.go 本地敏感词匹配
|
||
aliyun/text.go TextModerationPlus实现
|
||
```
|
||
|
||
统一接口需支持:商户、场景、操作人、业务对象、文本和 TraceID,返回 `allow/block/audit_only`、风险等级、标签和可审计的脱敏结果。
|
||
|
||
#### 7.2 消息发送接入
|
||
|
||
在 `Topic.handlePubBroadcast` 中,于 `saveAndBroadcastMessage` 之前:
|
||
|
||
1. 排除 WebRTC/系统信令。
|
||
2. 从 `msg.Pub.Content` 提取审核文本。
|
||
3. 执行本地规则和阿里云审核。
|
||
4. `block` 时通过 Tinode ctrl 返回业务错误,不调用保存和广播。
|
||
5. `allow/audit_only` 时继续现有消息链路。
|
||
|
||
消息编辑必须重新审核编辑后内容,不复用原消息结果。
|
||
|
||
#### 7.3 资料修改接入
|
||
|
||
用户昵称/签名、群名称/公告、群昵称的 HTTP 和 Tinode meta 修改通道必须接入同一 moderation service,防止通过另一个协议入口绕过审核。
|
||
|
||
#### 7.4 文本提取
|
||
|
||
- 普通文本:提取完整文本。
|
||
- Drafty 富文本:提取 `txt` 及按钮、链接的用户可见标题。
|
||
- 图片/文件/语音:提取文件名、说明和附带文本。
|
||
- 禁止将任意 JSON 直接字符串化后送审,避免审核协议元数据。
|
||
|
||
#### 7.5 阿里云结果映射
|
||
|
||
| 内部结果 | 动作 |
|
||
| --- | --- |
|
||
| `allow` | 继续保存和广播 |
|
||
| `block` | 不入库、不广播,返回统一业务错误 |
|
||
| `audit_only` | 允许业务继续,写审核日志 |
|
||
| `retryable_error` | 根据 `failure_mode` 放行或拦截 |
|
||
|
||
阿里云已测试样本表明,部分引流诈骗话术可能只出现在扩展判断中。实现时不能只查看单一 `RiskLevel/Label`,需保留脱敏原始响应以便调试,并使用真实样本建立回归集。
|
||
|
||
#### 7.6 超时、降级和熔断
|
||
|
||
- 阿里云请求不得在数据库事务中执行。
|
||
- 必须设置独立超时,禁止无限等待。
|
||
- 默认 `failure_mode=allow`,第三方异常时放行并告警,避免 IM 全局不可用。
|
||
- 连续失败达阈值后短时熔断,熔断期间按 `failure_mode` 处理。
|
||
- 本地敏感词检查不受阿里云熔断影响。
|
||
|
||
#### 7.7 缓存和多 Pod
|
||
|
||
- 阿里云全局配置、商户策略和敏感词分别缓存。
|
||
- 策略缓存键至少包含 `tenant_code + policy_version`。
|
||
- Kafka 配置事件和词库事件使所有 IM Pod 同时失效。
|
||
- 事件消费必须幂等,丢失事件后仍能通过 TTL 重新加载。
|
||
|
||
### 8. 审核日志
|
||
|
||
在 IM 业务库增加 `im_content_moderation_logs`:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `id` | 主键 |
|
||
| `request_id` | 内部审核请求 ID |
|
||
| `scene` | 用户昵称、单聊、群聊等 |
|
||
| `topic` / `seqid` | 关联消息,拦截时 `seqid` 可为空 |
|
||
| `actor_id` | 内容提交人 |
|
||
| `provider` | `local` / `aliyun_green` |
|
||
| `provider_request_id` | 阿里云 RequestID |
|
||
| `risk_level` | 统一风险等级 |
|
||
| `risk_labels` | 命中标签 JSON |
|
||
| `action` | `allow` / `block` / `audit_only` |
|
||
| `content_hash` | 原文 SHA-256 摘要 |
|
||
| `latency_ms` | 审核耗时 |
|
||
| `error_code` | 脱敏错误码 |
|
||
| `created_at` | 创建时间 |
|
||
|
||
表中不保存原始明文、AK/SK 或完整第三方响应中的敏感信息。
|
||
|
||
### 9. 错误码
|
||
|
||
| 错误 | 说明 | 客户端处理 |
|
||
| --- | --- | --- |
|
||
| `CONTENT_REJECTED` | 内容审核拒绝 | 保留未发送内容,提示用户修改 |
|
||
| `CONTENT_REVIEW_UNAVAILABLE` | 审核不可用且策略为拦截 | 提示稍后重试 |
|
||
|
||
不向客户端返回阿里云原始错误、命中词详情、AK/SK 或内部堆栈。
|
||
|
||
### 10. 监控
|
||
|
||
- 按商户/场景统计请求量、放行数、拦截数、审计数和降级数。
|
||
- 阿里云耗时 P50/P95/P99。
|
||
- 超时率、错误率、熔断状态和熔断持续时间。
|
||
- 按风险等级和标签统计命中数。
|
||
- 配置版本、各 Pod 最后刷新时间和缓存命中率。
|
||
- 监控标签不得放入原始消息文本。
|
||
|
||
### 11. 开发任务拆分
|
||
|
||
#### 11.1 Admin Server
|
||
|
||
1. 补齐 `moderation` 外部服务配置和真实连通性检查。
|
||
2. 实现商户审核策略表单和发布。
|
||
3. 限制默认商户以外的敏感词入口和后端权限。
|
||
4. 增加阿里云配置、商户策略和词库 internal API。
|
||
5. 发布并验证 Kafka 配置失效事件。
|
||
|
||
#### 11.2 IM Server
|
||
|
||
1. 实现 moderation 统一接口。
|
||
2. 实现阿里云 `TextModerationPlus` 客户端。
|
||
3. 实现商户策略、本地敏感词和阿里云配置缓存。
|
||
4. 接入消息发送、消息编辑、用户资料和群资料入口。
|
||
5. 增加审核日志表、错误映射、监控和告警。
|
||
6. 补齐单元测试、集成测试和阿里云沙箱/测试账号回归。
|
||
|
||
### 12. 测试重点
|
||
|
||
- 文本、Drafty、链接、表情、中英文混合和空内容提取。
|
||
- 同一内容在单聊、群聊和编辑入口处置一致。
|
||
- HTTP 和 Tinode meta 入口不能相互绕过。
|
||
- `default` 商户开启,其他商户关闭。
|
||
- 阿里云超时、500、限流、鉴权失败和熔断恢复。
|
||
- 本地词库命中、阿里云命中、双重命中和无命中。
|
||
- 多 Pod 收到配置发布后同步生效。
|
||
- 拦截消息不入库、不广播,不占用有效消息序号。
|
||
- 日志、Kafka、死信和监控中不出现消息明文和 AK/SK。
|
||
|
||
### 13. 验收标准
|
||
|
||
1. `default` 商户的用户昵称、群昵称、群名称、单聊和群聊均真实调用对应阿里云 Service。
|
||
2. 其他商户关闭审核时不调用阿里云。
|
||
3. 拦截内容不进入消息表,不向任何订阅者广播。
|
||
4. 本地敏感词与阿里云开关可独立启停。
|
||
5. 阿里云超时和异常严格按 `failure_mode` 处理。
|
||
6. 策略和词库发布后,所有 IM Pod 在规定时间内生效。
|
||
7. 日志能追溯商户、场景、用户、RequestID、风险结果和处置动作,但不保存明文。
|
||
8. 审核上线后独立稳定运行和观察,不依赖消息加密功能。
|
||
|
||
### 14. 发布顺序
|
||
|
||
1. 先发布 Admin Server 配置表单、internal API 和配置事件。
|
||
2. 发布 IM Server 审核模块,但保持所有商户策略关闭。
|
||
3. 对阿里云连通性、超时降级和多 Pod 配置同步进行生产前验证。
|
||
4. 仅对测试用户或小流量开启 `default` 商户审核。
|
||
5. 观察误拦截率、阿里云耗时、错误率和消息总延迟。
|
||
6. 完成全量开启和验收,进入独立稳定观察期。
|
||
7. 审核功能稳定并通过验收后,才能启动消息加密阶段。
|
||
|
||
## 第二部分:服务端消息加密存储(审核稳定后实施)
|
||
|
||
> 文档状态:第二阶段,内容审核稳定后实施
|
||
>
|
||
> 编制日期:2026-09-16
|
||
>
|
||
> 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server)
|
||
>
|
||
> 该改造会影响消息写入、历史同步、后台查询和数据迁移,必须单独发布和验收。
|
||
|
||
### 1. 实施前提
|
||
|
||
本阶段必须在《服务端内容审核改造文档》完成开发、全量上线和稳定观察后启动。
|
||
|
||
启动条件:
|
||
|
||
1. 内容审核的误拦截率、耗时、超时和降级策略已验收。
|
||
2. 审核与消息保存的边界已稳定,加密模块只接管“审核通过后的存储”。
|
||
3. 已完成 IM 数据库全量备份和可恢复性演练。
|
||
4. 已对 `im_messages`、`im_message_revisions`、`im_group_messages` 的数据量、存储量和迁移耗时完成评估。
|
||
|
||
### 2. 目标和非目标
|
||
|
||
#### 2.1 目标
|
||
|
||
- 审核通过的新消息在 IM Server 使用 AES-256-GCM 加密后落库。
|
||
- 国内 IM 业务库不保存可读消息正文。
|
||
- HotGo 库保存根密钥和加密后的商户数据密钥。
|
||
- 客户端协议不变,IM Server 对历史消息解密后按原协议返回。
|
||
- Admin Server 通过 IM internal-query 查看授权范围内的解密消息。
|
||
- 支持密钥版本、轮换、退役和历史数据迁移。
|
||
|
||
#### 2.2 非目标
|
||
|
||
- 不做客户端 AES 加密。
|
||
- 不做端到端加密(E2EE)。
|
||
- 不改变 HTTPS/WSS 传输安全方案。
|
||
- 不允许 Admin Server 直连 IM 数据库后自行解密。
|
||
- 不在日志、Kafka、死信和监控标签中保存消息明文。
|
||
|
||
### 3. 安全边界和部署
|
||
|
||
| 区域 | 系统 | 保存数据 |
|
||
| --- | --- | --- |
|
||
| 国外 | HotGo / Admin Server | 字典根密钥、商户消息密钥、加密开关和版本 |
|
||
| 国内 | IM Server | 用户、会话、消息密文和密钥版本 |
|
||
|
||
防护目标:任意一侧数据库单独泄露时,不能直接恢复消息明文。
|
||
|
||
已知边界:HotGo 数据库、IM 数据库和完整代码同时泄露不在本方案的主要防护范围内。
|
||
|
||
Admin Server 和 IM Server 跨公网通信必须使用 HTTPS、现有签名鉴权和重放防护,建议叠加 mTLS 或固定出口 IP 白名单。
|
||
|
||
### 4. 算法和存储格式
|
||
|
||
#### 4.1 算法
|
||
|
||
- 算法:AES-256-GCM。
|
||
- DEK:32 字节强随机密钥。
|
||
- Nonce:每次加密使用 `crypto/rand` 生成的独立 12 字节随机值。
|
||
- GCM 标签:使用 Go 标准库默认 16 字节认证标签。
|
||
- 明文:将原 `content` JSON 规范序列化为 UTF-8 字节。
|
||
- 禁止固定 Nonce、AES-ECB 和未认证的 AES-CBC/CTR。
|
||
|
||
#### 4.2 AAD
|
||
|
||
每条消息绑定:
|
||
|
||
```text
|
||
tenant_id | topic | seqid | from | client_msg_id | key_version | content
|
||
```
|
||
|
||
`content` 是固定字段标识。AAD 使用固定字段顺序和长度前缀编码,不使用可形成拼接歧义的简单字符串。
|
||
|
||
#### 4.3 密钥层级
|
||
|
||
```text
|
||
HotGo字典根密钥(KEK)
|
||
-> 加密每个商户、每个版本的DEK
|
||
-> DEK加密该商户消息
|
||
```
|
||
|
||
### 5. HotGo 密钥管理
|
||
|
||
#### 5.1 字典根密钥
|
||
|
||
根密钥使用 `hg_sys_dict_data` 保存,必须满足:
|
||
|
||
1. 使用独立内部字典类型,不出现在通用字典管理页。
|
||
2. 后端字典列表、选项、导出和缓存接口必须服务端拒绝该类型,不得仅隐藏菜单。
|
||
3. 根密钥是 32 字节强随机值的 Base64 编码,不使用口令、UUID 或可读文本。
|
||
4. 根密钥只由 Admin Server 专用密钥服务读取。
|
||
5. 根密钥不回显、不写日志、不进入 Kafka,不通过通用配置接口返回。
|
||
|
||
#### 5.2 商户密钥表
|
||
|
||
新增 `im_message_encryption_keys`:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | bigint | 主键 |
|
||
| `tenant_id` | bigint | 商户 ID |
|
||
| `key_version` | int | 密钥版本,从 1 递增 |
|
||
| `algorithm` | varchar(32) | 固定 `AES-256-GCM` |
|
||
| `encrypted_dek` | blob | KEK 加密后的 DEK |
|
||
| `dek_nonce` | varbinary(12) | 加密 DEK 时的随机 Nonce |
|
||
| `status` | varchar(16) | `pending` / `active` / `retired` / `disabled` |
|
||
| `created_at` | datetime | 创建时间 |
|
||
| `activated_at` | datetime | 启用时间 |
|
||
| `retired_at` | datetime | 退役时间 |
|
||
| `created_by` | bigint | 操作人 |
|
||
| `remark` | varchar(255) | 备注 |
|
||
|
||
约束:
|
||
|
||
- 唯一键:`tenant_id + key_version`。
|
||
- 每个商户同时只能有一把 `active` 密钥。
|
||
- `retired` 密钥可解密历史数据,不得加密新消息。
|
||
- `disabled` 只用于安全事故应急,禁用前必须完成历史转密钥。
|
||
|
||
#### 5.3 后台管理页
|
||
|
||
只允许最高权限平台管理员:
|
||
|
||
- 查看商户加密状态、当前密钥版本和最后轮换时间。
|
||
- 初始化商户密钥。
|
||
- 启用或暂停消息加密。
|
||
- 轮换密钥。
|
||
- 查看历史迁移进度。
|
||
|
||
页面不展示根密钥、DEK、`encrypted_dek` 和任何可恢复密钥的内容。
|
||
|
||
### 6. IM 数据库改造
|
||
|
||
#### 6.1 `im_messages` 和 `im_message_revisions`
|
||
|
||
新增:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `content_ciphertext` | longblob | 密文,包含 GCM 认证标签 |
|
||
| `content_nonce` | varbinary(12) | 每条消息独立随机 Nonce |
|
||
| `key_version` | int | 商户 DEK 版本 |
|
||
| `encryption_alg` | varchar(32) | `AES-256-GCM` |
|
||
| `encrypted` | tinyint | `0` 明文兼容,`1` 密文 |
|
||
|
||
迁移期规则:
|
||
|
||
- 新加密数据:`content=NULL`、`encrypted=1`。
|
||
- 旧数据:`content IS NOT NULL`、`encrypted=0`。
|
||
- 解密后必须恢复为原 `any` JSON 结构,不修改客户端协议。
|
||
- `head` 第一阶段不加密,但不得放置消息正文、联系方式等敏感明文。
|
||
|
||
#### 6.2 `im_group_messages`
|
||
|
||
`im_group_messages` 是群消息派生查询表,不得另存一份明文。优先方案:
|
||
|
||
- 仅保存索引、状态、审核和统计字段。
|
||
- 通过 `topic + seqid` 关联 `im_messages` 获取密文并解密。
|
||
- 迁移后清空或删除派生表中的明文 `content`。
|
||
|
||
如果必须保留独立内容列,则必须同步改为密文字段,禁止主表已加密而派生表仍保留明文。
|
||
|
||
#### 6.3 Schema 一致性
|
||
|
||
- MySQL 普通库和共享库/多商户 adapter 同时改造。
|
||
- 新建库 schema、启动 ensure 迁移和手工 SQL 脚本保持一致。
|
||
- `types.Message` 和 `types.MessageRevision` 增加密文元数据。
|
||
- Save、GetOne、GetAll、编辑、撤回、删除、修订记录和后台查询全部覆盖。
|
||
|
||
### 7. IM Server 加解密模块
|
||
|
||
```text
|
||
server/messagecrypto/
|
||
cipher.go AES-256-GCM加解密
|
||
aad.go AAD稳定编码
|
||
key_provider.go 密钥获取接口
|
||
key_cache.go tenant/version内存缓存
|
||
serializer.go content规范序列化
|
||
```
|
||
|
||
不将 AES 逻辑或 HotGo 调用直接写入 MySQL adapter。adapter 负责存储密文模型,加解密由业务存储服务边界统一执行。
|
||
|
||
#### 7.1 新消息
|
||
|
||
```text
|
||
完成现有校验
|
||
-> 完成内容审核
|
||
-> 分配seqid并组装AAD
|
||
-> 序列化content
|
||
-> 使用active DEK加密
|
||
-> 事务写入密文和元数据
|
||
-> 向在线订阅者广播内存中的原明文
|
||
```
|
||
|
||
审核必须发生在加密之前。广播使用请求内存中的明文,不执行“加密后立即解密”。
|
||
|
||
#### 7.2 历史消息和离线同步
|
||
|
||
```text
|
||
读取数据库记录
|
||
-> encrypted=0:读原content
|
||
-> encrypted=1:按tenant + key_version获取DEK
|
||
-> 重建AAD
|
||
-> GCM验签并解密
|
||
-> JSON反序列化
|
||
-> 按原Tinode协议返回
|
||
```
|
||
|
||
GCM 验签失败必须当作数据篡改或密钥错误,不得忽略错误或返回部分内容。
|
||
|
||
### 8. 密钥下发、缓存和轮换
|
||
|
||
#### 8.1 内部接口
|
||
|
||
| 方法 | 路径 | 用途 |
|
||
| --- | --- | --- |
|
||
| GET | `/internal/im-config/tenants/{code}/message-keys/active` | 获取当前 DEK |
|
||
| GET | `/internal/im-config/tenants/{code}/message-keys/{version}` | 获取指定版本 DEK |
|
||
| POST | `/internal/im-config/tenants/{code}/message-keys/ack` | IM Server 确认新密钥可用 |
|
||
|
||
这些接口只允许 IM Server 服务身份调用,不得暴露给浏览器或通用 Admin API。
|
||
|
||
#### 8.2 缓存
|
||
|
||
- IM Server 不得每条消息都跨境请求 HotGo。
|
||
- 按 `tenant_id + key_version` 在内存缓存已解密 DEK。
|
||
- 新消息使用 `active` 密钥,历史消息按存储版本取密钥。
|
||
- 每个 Pod 持有本地缓存,Kafka 事件使全部 Pod 失效。
|
||
- HotGo 短时不可用时,已有内存密钥的 Pod 可继续工作。
|
||
- 无可用密钥时禁止写入新消息,不得降级为明文落库。
|
||
|
||
#### 8.3 轮换
|
||
|
||
1. Admin Server 生成新 DEK,使用 KEK 加密后以 `pending` 入库。
|
||
2. 通知 IM Server 拉取、解包并完成加解密自检。
|
||
3. IM Server 回传 ACK 后,在事务中将旧版本设为 `retired`,新版本设为 `active`。
|
||
4. 新消息立即使用新版本,旧消息仍按旧版本解密。
|
||
5. 历史转密钥作为独立低速任务,不阻塞即时通信。
|
||
|
||
### 9. Admin 后台查看明文
|
||
|
||
- Admin Server 调用 IM internal admin-query,不直连 IM 库解密。
|
||
- IM Server 完成商户数据权限校验后,只解密当前分页数据。
|
||
- 记录管理员 ID、商户、IP、查询条件、返回数量、时间和 TraceID。
|
||
- 列表解密、详情解密和导出解密使用不同审计动作。
|
||
- 默认不提供明文批量导出。后续开放时必须增加独立权限和审批。
|
||
|
||
### 10. 关键词查询
|
||
|
||
加密后 MySQL 无法对密文执行 `LIKE '%keyword%'`。第一阶段采用:
|
||
|
||
1. 查询必须限定商户和时间范围,并尽量限定用户或群号。
|
||
2. IM Server 使用明文元数据缩小候选集。
|
||
3. 对有上限的候选集批量解密,在内存中执行关键词匹配。
|
||
4. 超过时间范围或候选数限制时返回明确提示。
|
||
|
||
第一版不建设明文分词索引。如后续必须支持全库高性能检索,再单独评审 HMAC 盲索引。
|
||
|
||
### 11. 历史数据迁移
|
||
|
||
#### 11.1 发布顺序
|
||
|
||
1. 发布 HotGo 密钥结构、密钥服务和管理页。
|
||
2. 为默认商户初始化第 1 版 DEK,但不开启加密写入。
|
||
3. 发布 IM Server 双读版本,验证密钥获取、加解密和所有读写链路。
|
||
4. 小流量开启新消息加密,旧数据仍可明文读取。
|
||
5. 全量开启新消息加密后,启动历史数据分批迁移。
|
||
6. 数量对账、抽样解密和 GCM 验签通过后,逐批清空明文 `content`。
|
||
7. 观察期结束后,再决定是否删除原明文字段。
|
||
|
||
#### 11.2 迁移任务
|
||
|
||
- 按主键或 `topic + seqid` 分批,不长时锁表。
|
||
- 每批独立事务,记录游标、成功数、失败数和最后错误。
|
||
- 重复执行幂等,`encrypted=1` 直接跳过。
|
||
- 先写密文并验证可解密,再将明文置空。
|
||
- 失败记录保留原数据并进入重试列表,不得丢失数据。
|
||
- 限制迁移速率,监控数据库负载和即时消息延迟。
|
||
|
||
### 12. 错误处理
|
||
|
||
| 错误 | 说明 | 客户端/后台处理 |
|
||
| --- | --- | --- |
|
||
| `MESSAGE_ENCRYPTION_UNAVAILABLE` | 未取得当前加密密钥 | 暂停新消息写入,不明文降级 |
|
||
| `MESSAGE_DECRYPT_FAILED` | 验签或解密失败 | 显示“消息暂时无法加载”,不返回乱码 |
|
||
| `MESSAGE_KEY_VERSION_NOT_FOUND` | 历史密钥版本不存在 | 报警并阻止该记录解密 |
|
||
|
||
客户端不参与加解密,不返回密钥版本、Nonce、密文、解密堆栈或内部错误。
|
||
|
||
### 13. 监控和审计
|
||
|
||
- 加密/解密成功数、失败数和耗时。
|
||
- 各商户当前密钥版本。
|
||
- 密钥获取成功率、缓存命中率和最后刷新时间。
|
||
- 明文历史记录剩余数、迁移速率和迁移失败数。
|
||
- GCM 验签失败产生高优先级告警。
|
||
- 后台明文查看记录管理员、商户、IP、查询范围、返回数量和 TraceID。
|
||
- 监控标签和日志不得保存消息正文、密文、Nonce、DEK 或 KEK。
|
||
|
||
### 14. 开发任务拆分
|
||
|
||
#### 14.1 Admin Server
|
||
|
||
1. 根密钥字典隔离和通用接口防护。
|
||
2. `im_message_encryption_keys` 及业务逻辑。
|
||
3. 消息加密管理页面和最高权限控制。
|
||
4. 密钥下发、ACK 和 Kafka 失效事件。
|
||
5. 密钥初始化、轮换、退役和审计。
|
||
|
||
#### 14.2 IM Server
|
||
|
||
1. AES-256-GCM、AAD 和规范序列化模块。
|
||
2. 密钥获取、多版本缓存和多 Pod 失效。
|
||
3. 新消息加密写入和双读解密。
|
||
4. 单聊、群聊、修订、离线同步和后台查询覆盖。
|
||
5. 历史数据迁移、进度统计和失败重试。
|
||
6. 加解密监控、验签告警和后台解密审计。
|
||
|
||
### 15. 测试重点
|
||
|
||
- 相同明文连续加密两次,Nonce 和密文不同。
|
||
- 修改密文、Nonce、AAD 或 `key_version` 任意一项后解密失败。
|
||
- JSON 字符串、数组、对象、Drafty、空值和超长内容往返一致。
|
||
- 在线发送、离线同步、多端漫游、编辑、回复、撤回和删除行为不变。
|
||
- 新旧消息混合分页和历史同步。
|
||
- 新旧密钥版本并存和多 Pod 切换。
|
||
- HotGo 暂时不可用、IM Pod 重启且无缓存、密钥版本缺失。
|
||
- 历史迁移中断、重试、幂等和数量对账。
|
||
- 旧密钥退役后可读旧消息,新消息只使用新密钥。
|
||
- 日志、Kafka、错误响应和监控中无明文和密钥。
|
||
|
||
### 16. 验收标准
|
||
|
||
1. 开启商户的新消息在数据库中不存在可读正文。
|
||
2. 相同内容的 Nonce 和密文不同。
|
||
3. 篡改密文、Nonce 或 AAD 后必须验签失败。
|
||
4. 所有在线和历史消息功能保持原行为。
|
||
5. Admin 有权限账号可查看当页解密明文,无权限账号被拒绝。
|
||
6. 后台明文查看形成完整审计记录。
|
||
7. 旧密钥退役后仍可解密旧消息,新消息只使用新版本。
|
||
8. HotGo 短时断开时,有内存密钥的 Pod 可工作;无密钥时不得明文降级。
|
||
9. 历史数据迁移完成后,`im_messages`、`im_message_revisions`、`im_group_messages` 无明文副本。
|
||
10. 纯 AES 加解密不得成为消息链路主要瓶颈,密钥缓存命中时不产生跨境请求。
|
||
|
||
### 17. 上线和回滚
|
||
|
||
1. 第一次只为测试商户或小流量用户开启新消息加密。
|
||
2. 加密开关必须按商户生效,不做全局一次性切换。
|
||
3. 观察写入失败、解密失败、同步延迟、数据库负载和密钥缓存。
|
||
4. 未开始历史迁移前,可停止新加密写入,但已加密消息仍必须保持可解密读取。
|
||
5. 禁止通过删除旧密钥实现回滚。
|
||
6. 历史迁移开始后,回滚必须保留双读能力,不允许只回滚数据库结构或只回滚代码。
|