obs-agent 链路消息加密¶
本文面向部署和使用 Beak 的用户,说明 Beak 与自部署 obs-agent 之间如何加密通信,以及需要配置什么。
简介¶
Beak 在已有 HTTPS/WSS 之上,对 Beak 和 obs-agent 之间的聊天正文再加一层应用层加密。链路中间人即使截获 WebSocket 消息,也只能看到 base64 编码的密文和少量路由字段,看不到用户消息、agent 回复、thinking、plan、task insight 或 tool call 里的自然语言内容。
这不是完整端到端加密。Beak 服务端仍是可信边界:Beak 会解密消息、保存历史、审计、搜索,并把明文展示给 Web UI、obscli 和 IM channel。
保护边界¶
flowchart LR
U[用户<br/>Web UI / obscli / IM] -->|HTTPS<br/>明文业务 API| B[Beak 服务端<br/>可信边界]
B -->|WSS + HPKE 应用层密文| A[自部署 obs-agent]
A -->|WSS + HPKE 应用层密文| B
B -->|HTTPS<br/>明文展示/历史/审计| U
M((链路中间人))
M -.只能看到密文.-> B
M -.只能看到密文.-> A
加密目标是保护 Beak 与 obs-agent 链路,不是让 Beak 自己看不到明文。因此:
- Web UI、obscli、IM channel 不需要做加解密改造。
- Beak 数据库中的消息历史仍按现有明文语义保存。
- 自部署 agent 收到消息后会解密,再交给 LLM 和本地工具流程。
- agent 回传给 Beak 的自然语言 payload 会先加密,Beak 解密后再广播给用户侧。
使用的加密方案¶
当前实现使用 HPKE Auth 模式,算法标识为:
HPKE 是 IETF RFC 9180 定义的公钥消息加密封装。简单理解:
- 接收方持有私钥,发布公钥。
- 发送方使用接收方公钥加密消息。
- 接收方使用自己的私钥解密消息。
- Auth 模式还会绑定发送方身份,接收方可以确认消息来自预期发送方 key。
哪些内容会被加密¶
启用后,以下 Beak 与 obs-agent 链路 payload 会加密:
- Beak 发给 agent 的
chat:user - agent 发给 Beak 的
chat:agent - agent 发送的
chat:agent_thinking - agent 发送的
chat:agent_tool_call - agent 发送的
chat:agent_event中包含的自然语言/业务 payload,例如 plan、task insight
加密前不是只加密 content 字段,而是把业务 content 和业务 metadata 一起编码为 JSON 后加密:
链路上的 WebSocket message 仍保留最小路由字段,例如 message ID、session ID、sender ID、message type 和 metadata.message_encryption。这些字段用于路由和校验,不承载用户正文。
查看链路上的消息结构¶
加密后的 content 是 base64 编码的密文字节,不是可读 JSON。metadata.message_encryption 是公开 envelope,用来告诉接收方使用哪个 key、哪个算法和哪个方向解密。
示例结构:
{
"type": "chat:user",
"id": "msg_123",
"session_id": "session_abc",
"sender_id": "user:u1",
"receiver_id": "agent_xxx",
"content": "BASE64_CIPHERTEXT",
"metadata": {
"message_encryption": {
"version": "v1",
"alg": "hpke-auth-x25519-hkdf-sha256-aes-256-gcm",
"key_id": "hpke-agent-key",
"sender_key_id": "beak-2026-06",
"direction": "beak_to_agent",
"enc": "BASE64_HPKE_ENCAPSULATED_KEY",
"expires_at": "2026-06-30T10:05:00Z"
}
}
}
content 和 enc 都是 base64 字符串。它们可以安全地通过 JSON/WebSocket 传输,但不能解读为明文。
密钥如何交换¶
Beak 和 agent 各自维护自己的接收密钥。原则是:谁负责解密,谁持有私钥。
sequenceDiagram
autonumber
participant Agent as obs-agent
participant Beak as Beak
Agent->>Agent: 启动时读取或生成 agent 私钥
Agent->>Beak: control:connect<br/>metadata.message_encryption={agent key_id, agent public_key}
Beak->>Beak: 记录当前连接上的 agent public key
Beak-->>Agent: ack<br/>metadata.message_encryption={beak key_id, beak public_key}
Agent->>Agent: 只在内存中保存 Beak public key
关键点:
- Agent 私钥默认保存在本机
AGENT_MESSAGE_HPKE_KEY_PATH,默认路径是/var/lib/obs-agent/message-hpke-key.json。 - 如果 key 文件不存在,agent 会自动生成一对 key,并以
0600权限写入该文件。 - Beak 不生成、不保存 agent 私钥。
- Beak public key 通过连接 ack 下发给 agent;agent 不把 Beak public key 写进本地 key 文件,只保存在内存里。
- 如果 agent 长连接期间刷新了自己的 key,会发送
control:message_key_update,Beak 从该消息开始使用新 public key。
跟踪消息加密和解密流程¶
sequenceDiagram
autonumber
participant User as 用户侧客户端
participant Beak as Beak
participant Agent as obs-agent
participant LLM as LLM/工具
User->>Beak: 发送用户消息
Beak->>Beak: 保存明文历史并选择目标 agent
Beak->>Beak: 用 agent public key 加密 JSON payload
Beak->>Agent: chat:user<br/>content=BASE64_CIPHERTEXT
Agent->>Agent: 用 agent private key 解密
Agent->>LLM: 明文进入 agent runtime
LLM-->>Agent: 生成回复/事件/tool call
Agent->>Agent: 用 Beak public key 加密 JSON payload
Agent->>Beak: chat:agent / thinking / tool_call / event<br/>content=BASE64_CIPHERTEXT
Beak->>Beak: 用 Beak private key 解密并校验上下文
Beak-->>User: 明文展示、历史、审计、stream
Beak 与 obs-agent 链路上的中间人只能看到密文、路由字段和 envelope。Beak 和 agent 两端会在各自可信边界内还原明文。
AAD 校验保护什么¶
AAD 是 Additional Authenticated Data,意思是“不会被加密、但会被认证的数据”。它用于把密文绑定到具体上下文,防止攻击者把一条密文复制到另一个 deployment、workspace、agent、session 或方向里复用。
当前统一使用这些 AAD 字段:
workspace_uuiddeployment_idagent_uuidsession_uuidmessage_uuidmessage_typedirectionexpires_atversionkey_idalgorithm
这些字段一旦被篡改,解密会失败。不同消息类型不会使用不同 AAD schema;tool call、plan、thinking 等业务细节都放在加密 payload 内,避免协议随业务字段变化而变得不稳定。
flowchart TD
C[密文 content] --> D{解密前校验}
E[message_encryption envelope] --> D
A[AAD 上下文<br/>deployment / workspace / agent / session / message / direction / expires_at] --> D
D -->|全部匹配| P[解出 JSON payload]
D -->|任一字段不匹配| R[拒绝消息<br/>结构化错误]
过期和 replay 防护¶
每条加密 envelope 都带 expires_at。当前默认有效期是 5 分钟。过期后接收方拒绝解密。
接收方还会记录短时间内见过的密文 fingerprint,防止同一进程内重复处理同一条密文。这是进程内 replay 防护,不承诺跨 Beak pod、跨 agent 重启或跨共享存储的全局 replay 防护。
部署 Beak¶
Beak 默认启用消息加密能力。启用时必须配置 Beak 接收私钥,否则 Beak 会启动失败,并提示缺少 BEAK_MESSAGE_HPKE_KEYS_JSON or BEAK_MESSAGE_HPKE_PRIVATE_KEY。
生成 Beak HPKE key:
输出示例:
{
"key_id": "beak-2026-06",
"private_key": "BASE64_PRIVATE_KEY",
"public_key": "BASE64_PUBLIC_KEY",
"algorithm": "hpke-auth-x25519-hkdf-sha256-aes-256-gcm",
"status": "active"
}
Kubernetes 部署推荐把 Beak key ring 放入 Secret:
kubectl create secret generic beak-message-hpke \
--from-literal=BEAK_MESSAGE_HPKE_ACTIVE_KEY_ID=beak-2026-06 \
--from-literal=BEAK_MESSAGE_HPKE_KEYS_JSON='[{"key_id":"beak-2026-06","private_key":"BASE64_PRIVATE_KEY","status":"active"}]'
Deployment 引用:
只有临时排障或兼容旧部署时,才建议显式关闭:
部署 agent¶
Agent 默认启用消息加密:
通常不需要配置 agent 私钥。agent 会使用:
如果文件不存在,agent 会自动生成:
{
"version": "v1",
"algorithm": "hpke-auth-x25519-hkdf-sha256-aes-256-gcm",
"key_id": "hpke-...",
"private_key": "BASE64_PRIVATE_KEY",
"public_key": "BASE64_PUBLIC_KEY"
}
目录和文件权限要求:
- key 目录:
0700 - key 文件:
0600 - agent 运行用户需要可读写该路径
如需关闭某个 agent 的链路加密:
这是 agent 级开关。Beak 可以继续服务该 agent 的明文兼容连接,但会记录可观测日志和指标;已声明启用加密的连接如果加解密失败,不会静默降级成明文。
轮换密钥¶
Beak key ring 支持三种状态:
active:用于新消息,也可解密。decrypt_only:只用于解密旧消息,不再用于新消息。retired:不再用于普通消息解密。
常规 Beak key 轮换流程:
sequenceDiagram
autonumber
participant Ops as 运维
participant Secret as K8s Secret / Vault
participant Beak as Beak pods
participant Agent as agents
Ops->>Ops: 生成新 Beak key
Ops->>Secret: 新 key=active<br/>旧 key=decrypt_only
Ops->>Beak: 滚动重启
Beak-->>Agent: 新连接 ack 下发新 Beak public key
Agent->>Beak: 后续消息使用新 key_id 加密
Ops->>Secret: grace period 后移除或 retired 旧 key
Ops->>Beak: 再次滚动重启
Agent key 可以手工删除/替换 key 文件后重启,也可以配置自动轮换:
默认 0 表示不自动轮换。agent 如果在长连接期间轮换 key,会发送 control:message_key_update 通知 Beak 使用新的 agent public key。
日志与排障¶
常见问题:
- Beak 启动失败,提示缺少
BEAK_MESSAGE_HPKE_KEYS_JSON or BEAK_MESSAGE_HPKE_PRIVATE_KEY:配置 Beak key ring,或临时设置BEAK_MESSAGE_ENCRYPTION_ENABLED=false。 - Agent 收不到加密用户消息:检查 agent 是否上报了
control:connect.metadata.message_encryption,其中必须有key_id和public_key。 - Agent 无法加密回包:检查 Beak ack 是否包含
metadata.message_encryption和workspace_uuid。 - Beak 解密失败:检查 envelope 中的
key_id是否存在于 Beak key ring,且状态是active或decrypt_only。 - 看到 replay 或 expired 错误:检查消息是否重复发送、系统时间是否漂移、消息是否超过默认 5 分钟有效期。
debug 日志可以记录完整密文和完整 metadata.message_encryption,便于本地测试和受控排障;info/warn/error 日志不应记录聊天明文或私钥。生产环境不要长期打开 debug 日志。
和完整 E2EE 的区别¶
| 能力 | Beak 与 obs-agent 链路加密 | 完整端到端加密 |
|---|---|---|
| 防链路 MITM 读取 Beak 与 obs-agent 消息 | 是 | 是 |
| Beak 服务端可见明文 | 是 | 否 |
| Web UI / obscli / IM 需要改造 | 否 | 是 |
| Beak 可继续做历史、审计、搜索、路由 | 是 | 需要重新设计 |
| 第一版实现范围 | 已实现核心链路 | 非目标 |
当前方案优先解决自部署 agent 链路暴露风险,同时保持现有客户端和 Beak 服务端能力不变。