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분입니다. 수신자는 만료 후 복호화를 거부합니다.
수신자는 또한 짧은 시간 창 내에 확인된 암호문 지문을 기록하여 동일한 프로세스 내에서 같은 암호문을 두 번 처리하는 것을 방지합니다. 이는 프로세스 내 재생 공격 방어입니다. Beak 파드, 에이전트 재시작 또는 공유 스토리지 전반에 걸친 글로벌 재생 공격 방어를 보장하지는 않습니다.
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 키 링은 세 가지 상태를 지원합니다:
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 서버 기능을 변경하지 않으면서 자체 호스팅 에이전트 링크 노출 보호를 우선시합니다.