obs-agent メッセージリンク暗号化¶
本ドキュメントは Beak をデプロイ・利用するユーザーを対象としています。Beak とセルフホスト型 obs-agent 間の通信暗号化の仕組みと、必要な設定について説明します。
概要¶
既存の HTTPS/WSS に加え、Beak は Beak と obs-agent 間のチャットペイロードに対してアプリケーション層の暗号化をもう一層追加します。中間者が WebSocket メッセージを傍受しても、base64 エンコードされた暗号文と少数のルーティングフィールドしか確認できません。ユーザーメッセージ、エージェント応答、思考、プラン、タスクインサイト、ツールコール内の自然言語コンテンツは読み取れません。
これは完全なエンドツーエンド暗号化ではありません。Beak サーバーが信頼境界として機能します。Beak はメッセージを復号し、履歴の保存、監査、検索を行い、Web UI、obscli、IM チャネルに平文を表示します。
保護境界¶
flowchart LR
U[User<br/>Web UI / obscli / IM] -->|HTTPS<br/>Plain business API| B[Beak Server<br/>Trusted boundary]
B -->|WSS + HPKE application ciphertext| A[Self-hosted obs-agent]
A -->|WSS + HPKE application ciphertext| B
B -->|HTTPS<br/>Plain display/history/audit| U
M((Man-in-the-middle))
M -.Can only see ciphertext.-> B
M -.Can only see ciphertext.-> A
暗号化の目的は obs-agent リンクを保護することであり、Beak 自体から平文を隠すことではありません。そのため:
- Web UI、obscli、IM チャネルは暗号化/復号の変更を必要としません。
- Beak データベースのメッセージ履歴は既存の平文セマンティクスで保存されます。
- セルフホスト型エージェントは、受信メッセージを LLM やローカルツールに渡す前に復号します。
- エージェントから返される自然言語ペイロードは、Beak に送信される前に暗号化されます。Beak はそれらを復号し、ユーザー側クライアントにブロードキャストします。
暗号化方式¶
現在の実装は HPKE Auth モードを使用しています。アルゴリズム識別子は以下のとおりです:
HPKE は IETF RFC 9180 で定義された公開鍵メッセージ暗号化です。簡単に言えば:
- 受信者は秘密鍵を所有し、公開鍵を公開します。
- 送信者は受信者の公開鍵でメッセージを暗号化します。
- 受信者は自身の秘密鍵でメッセージを復号します。
- Auth モードでは送信者の ID もバインドされるため、受信者はメッセージが期待される送信者鍵から送られたことを確認できます。
暗号化対象コンテンツ¶
暗号化が有効化されると、以下の obs-agent リンクペイロードが暗号化されます:
- Beak からエージェントに送信される
chat:user - エージェントから Beak に送信される
chat:agent - エージェントが送信する
chat:agent_thinking - エージェントが送信する
chat:agent_tool_call chat:agent_event内の自然言語またはビジネスペイロード(プランやタスクインサイトなど)
暗号化前、Beak は content フィールドのみを暗号化するのではありません。ビジネス content とビジネス metadata の両方を JSON としてエンコードし、その JSON を暗号化します:
{
"content": "Plaintext body",
"metadata": {
"event_type": "task_insight",
"tool_call_content": "..."
}
}
リンク上の WebSocket メッセージには、メッセージ ID、セッション ID、送信者 ID、メッセージタイプ、metadata.message_encryption など最小限のルーティングフィールドが残ります。これらのフィールドはルーティングと検証に使用され、ユーザーテキストは含みません。
ワイヤ上のメッセージ構造¶
暗号化された content は base64 エンコードされた暗号文バイトであり、読み取り可能な JSON ではありません。metadata.message_encryption は公開エンベロープで、受信者に復号に使用する鍵、アルゴリズム、方向を伝えます。
構造の例:
{
"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 とエージェントはそれぞれ自身の受信用鍵を管理します。ルールは:復号する側が秘密鍵を所有する、というものです。
sequenceDiagram
autonumber
participant Agent as obs-agent
participant Beak as Beak
Agent->>Agent: Read or generate agent private key on startup
Agent->>Beak: control:connect<br/>metadata.message_encryption={agent key_id, agent public_key}
Beak->>Beak: Record agent public key on current connection
Beak-->>Agent: ack<br/>metadata.message_encryption={beak key_id, beak public_key}
Agent->>Agent: Store Beak public key in memory only
要点:
- エージェント秘密鍵は
AGENT_MESSAGE_HPKE_KEY_PATHにローカル保存されます。デフォルトパスは/var/lib/obs-agent/message-hpke-key.jsonです。 - 鍵ファイルが存在しない場合、エージェントは自動的に鍵ペアを生成し、
0600パーミッションで書き込みます。 - Beak はエージェント秘密鍵を生成・保存しません。
- Beak 公開鍵は接続 ack を通じてエージェントに送信されます。エージェントはメモリ内のみに保持し、ローカル鍵ファイルには書き込みません。
- エージェントが長時間接続中に自身の鍵を更新する場合、
control:message_key_updateを送信します。Beak はそのメッセージ以降、新しい公開鍵を使用します。
メッセージ暗号化・復号のトレース¶
sequenceDiagram
autonumber
participant User as User-side client
participant Beak as Beak
participant Agent as obs-agent
participant LLM as LLM/Tools
User->>Beak: Send user message
Beak->>Beak: Store plaintext history and choose target agent
Beak->>Beak: Encrypt JSON payload with agent public key
Beak->>Agent: chat:user<br/>content=BASE64_CIPHERTEXT
Agent->>Agent: Decrypt with agent private key
Agent->>LLM: Plaintext enters agent runtime
LLM-->>Agent: Generate reply/event/tool call
Agent->>Agent: Encrypt JSON payload with Beak public key
Agent->>Beak: chat:agent / thinking / tool_call / event<br/>content=BASE64_CIPHERTEXT
Beak->>Beak: Decrypt with Beak private key and validate context
Beak-->>User: Plain display, history, audit, stream
obs-agent リンク上の中間者は暗号文、ルーティングフィールド、エンベロープしか確認できません。Beak とエージェントはそれぞれ自身の信頼境界内で平文を復元します。
AAD 保護¶
AAD は Additional Authenticated Data(追加認証データ)を意味します。暗号化はされませんが認証されるデータです。暗号文を特定のコンテキストにバインドし、攻撃者が暗号文を別のデプロイメント、ワークスペース、エージェント、セッション、方向にコピーすることを防ぎます。
現在の統一 AAD フィールドは以下のとおりです:
workspace_uuiddeployment_idagent_uuidsession_uuidmessage_uuidmessage_typedirectionexpires_atversionkey_idalgorithm
これらのフィールドのいずれかが改ざんされると、復号は失敗します。異なるメッセージタイプでも AAD スキーマは異なりません。ツールコール、プラン、思考などのビジネス詳細は暗号化ペイロード内に配置されるため、ビジネスフィールドの変更に伴いプロトコルが不安定になることはありません。
flowchart TD
C[Ciphertext content] --> D{Validate before decryption}
E[message_encryption envelope] --> D
A[AAD context<br/>deployment / workspace / agent / session / message / direction / expires_at] --> D
D -->|All match| P[Decode JSON payload]
D -->|Any mismatch| R[Reject message<br/>structured error]
有効期限とリプレイ保護¶
各暗号化エンベロープには expires_at があります。現在のデフォルト有効期間は 5 分です。受信者は有効期限を過ぎた復号を拒否します。
受信者は短い時間ウィンドウ内に確認された暗号文フィンガープリントも記録し、1 プロセス内で同じ暗号文を 2 回処理することを回避します。これはインプロセスのリプレイ保護です。Beak Pod 間、エージェント再起動間、共有ストレージ間でのグローバルなリプレイ保護は保証しません。
Beak のデプロイ¶
Beak はデフォルトでメッセージ暗号化を有効にしています。有効化されている場合、Beak には受信用秘密鍵の設定が必要です。設定がない場合、Beak は起動に失敗し BEAK_MESSAGE_HPKE_KEYS_JSON or BEAK_MESSAGE_HPKE_PRIVATE_KEY が不足している旨を報告します。
Beak HPKE 鍵の生成:
出力例:
{
"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 キーリングを 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 で参照します:
一時的なトラブルシューティングや旧デプロイメントとの互換性のためにのみ、明示的に暗号化を無効化します:
エージェントのデプロイ¶
エージェントのメッセージ暗号化はデフォルトで有効です:
通常、エージェント秘密鍵の設定は不要です。エージェントは以下を使用します:
ファイルが存在しない場合、エージェントは自動的に生成します:
{
"version": "v1",
"algorithm": "hpke-auth-x25519-hkdf-sha256-aes-256-gcm",
"key_id": "hpke-...",
"private_key": "BASE64_PRIVATE_KEY",
"public_key": "BASE64_PUBLIC_KEY"
}
ディレクトリおよびファイルのパーミッション:
- 鍵ディレクトリ:
0700 - 鍵ファイル:
0600 - エージェントランタイムユーザーがこのパスの読み書きができる必要があります。
特定のエージェントでリンク暗号化を無効にするには:
これはエージェントレベルのスイッチです。Beak は引き続きそのエージェントを平文互換接続として処理し、オブザーバビリティログとメトリクスを記録します。暗号化有効を宣言した接続は、暗号化または復号が失敗しても平文へサイレントにダウングレードされません。
鍵ローテーション¶
Beak キーリングは 3 つのステータスをサポートします:
active:新しいメッセージに使用され、復号も可能。decrypt_only:古いメッセージの復号のみ行い、新しいメッセージには使用されない。retired:通常メッセージの復号に使用されなくなった。
通常の Beak 鍵ローテーションフロー:
sequenceDiagram
autonumber
participant Ops as Ops
participant Secret as K8s Secret / Vault
participant Beak as Beak pods
participant Agent as agents
Ops->>Ops: Generate new Beak key
Ops->>Secret: New key=active<br/>old key=decrypt_only
Ops->>Beak: Rolling restart
Beak-->>Agent: New connection ack sends new Beak public key
Agent->>Beak: Later messages use new key_id
Ops->>Secret: Remove or retire old key after grace period
Ops->>Beak: Rolling restart again
エージェント鍵は、鍵ファイルを削除または置換してエージェントを再起動することで手動ローテーションできます。または以下の設定で自動ローテーションが可能です:
デフォルトの 0 は自動ローテーション無効を意味します。エージェントが長時間接続中に鍵をローテーションした場合、control:message_key_update を送信して Beak が新しいエージェント公開鍵を使用できるようにします。
ログとトラブルシューティング¶
よくある問題:
- Beak が起動に失敗し
BEAK_MESSAGE_HPKE_KEYS_JSON or BEAK_MESSAGE_HPKE_PRIVATE_KEYが不足していると報告される:Beak キーリングを設定するか、一時的にBEAK_MESSAGE_ENCRYPTION_ENABLED=falseを設定します。 - エージェントが暗号化されたユーザーメッセージを受信できない:エージェントが
control:connect.metadata.message_encryptionを報告したか確認します。key_idとpublic_keyが含まれている必要があります。 - エージェントが応答を暗号化できない:Beak ack に
metadata.message_encryptionとworkspace_uuidが含まれているか確認します。 - Beak の復号が失敗する:エンベロープの
key_idが Beak キーリングに存在し、ステータスがactiveまたはdecrypt_onlyであるか確認します。 - リプレイまたは期限切れエラーが発生する:メッセージが繰り返し送信されていないか、システム時刻のずれが存在しないか、メッセージがデフォルトの 5 分間の有効期間を超過していないか確認します。
デバッグログはローカルテストや管理されたトラブルシューティングのために、完全な暗号文と完全な metadata.message_encryption を記録できます。Info/warn/error ログはチャット平文や秘密鍵を記録すべきではありません。本番環境でデバッグログを長期間有効にしないでください。
完全な E2EE との違い¶
| 機能 | obs-agent リンク暗号化 | 完全なエンドツーエンド暗号化 |
|---|---|---|
| 中間者による obs-agent メッセージの読み取りを防止 | はい | はい |
| Beak サーバーが平文を確認可能 | はい | いいえ |
| Web UI / obscli / IM の変更が必要 | いいえ | はい |
| Beak が履歴、監査、検索、ルーティングを維持可能 | はい | 再設計が必要 |
| 初期実装スコープ | コアリンクを実装済み | 目標外 |
現在の方式は、既存のクライアントと Beak サーバーの機能を変更せずに、セルフホスト型エージェントリンクの露出を保護することを優先しています。