跳转至

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-auth-x25519-hkdf-sha256-aes-256-gcm

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 后加密:

{
  "content": "明文正文",
  "metadata": {
    "event_type": "task_insight",
    "tool_call_content": "..."
  }
}

链路上的 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"
    }
  }
}

contentenc 都是 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_uuid
  • deployment_id
  • agent_uuid
  • session_uuid
  • message_uuid
  • message_type
  • direction
  • expires_at
  • version
  • key_id
  • algorithm

这些字段一旦被篡改,解密会失败。不同消息类型不会使用不同 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:

beak -generate-message-hpke-key -message-hpke-key-id beak-2026-06

输出示例:

{
  "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 引用:

envFrom:
  - secretRef:
      name: beak-message-hpke

只有临时排障或兼容旧部署时,才建议显式关闭:

env:
  - name: BEAK_MESSAGE_ENCRYPTION_ENABLED
    value: "false"

部署 agent

Agent 默认启用消息加密:

AGENT_MESSAGE_ENCRYPTION_ENABLED=true

通常不需要配置 agent 私钥。agent 会使用:

AGENT_MESSAGE_HPKE_KEY_PATH=/var/lib/obs-agent/message-hpke-key.json

如果文件不存在,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_MESSAGE_ENCRYPTION_ENABLED=false

这是 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 文件后重启,也可以配置自动轮换:

AGENT_MESSAGE_HPKE_ROTATION_INTERVAL=2160h

默认 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_idpublic_key
  • Agent 无法加密回包:检查 Beak ack 是否包含 metadata.message_encryptionworkspace_uuid
  • Beak 解密失败:检查 envelope 中的 key_id 是否存在于 Beak key ring,且状态是 activedecrypt_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 服务端能力不变。

文档评价

文档内容是否对您有帮助? ×