본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 02. 10:50

kioku-mesh #5 - mTLS로 kioku-mesh 메쉬 보안 강화하기

요약

kioku-mesh의 보안을 강화하기 위해 mTLS(상호 TLS)를 도입하는 방법을 설명합니다. 네트워크 계층의 신뢰에 의존하지 않고, 자체 CA를 통해 인증된 피어만 메쉬에 참여할 수 있도록 설계하는 가이드를 제공합니다.

핵심 포인트

  • mTLS 도입으로 네트워크 보안 취약점 보완
  • 자체 CA 운영을 통한 완전한 제어권 확보
  • 비밀키 유출 방지를 위한 CSR 기반 인증 프로세스
  • zenohd 설정을 통한 mTLS 모드 전환 방법

왜 mTLS를 추가하는가

kioku-mesh의 기본 설계는 신뢰할 수 있는 네트워크 위에서 동작한다는 전제를 가지고 있습니다. 폐쇄된 LAN, Tailscale, WireGuard 등을 통해 7447/tcp를 제한하는 것을 전제로 하며, 대부분의 개인 메쉬(Personal Mesh)에는 그것만으로도 충분합니다.

하지만 다음과 같은 상황에서는 네트워크 계층의 신뢰만으로는 부족합니다.

  • Tailscale ACL 설정 실수로 인해 원래 연결해서는 안 되는 노드에 도달할 수 있게 된 경우
  • 거주자나 동료와 LAN을 공유하고 있는 경우
  • VPN을 해지하거나 갱신하는 타이밍에 일시적으로 "신뢰할 수 있는 네트워크"라는 전제가 무너지는 경우

mTLS를 도입하면, zenohd는 자신의 private CA가 서명한 인증서를 가지고 있지 않은 피어(Peer)를 모두 거부합니다. 네트워크가 도달하더라도 인증서가 없다면 메쉬에 참여할 수 없습니다.

설계의 포인트는 다음 세 가지입니다.

  • CA는 직접 구축한다 (SaaS나 Let's Encrypt를 사용하지 않고 완전히 자체적으로 운영)
  • 비밀키는 호스트 밖으로 나가지 않는다 (주고받는 것은 CSR과 서명된 인증서뿐이며, 둘 다 비공개 정보가 아님)
  • 루프백(Loopback)은 평문 그대로 유지한다 (동일 호스트 상의 CLI / MCP 클라이언트는 127.0.0.1에서 TCP 그대로 연결)

구성

연재 제4회의 3대 구성을 그대로 사용합니다.

호스트명역할LAN IPmTLS 상의 역할
desktophub192.168.1.10CA 호스트 겸임
laptopspoke192.168.1.11엔드 엔티티 (End Entity)
agent-serverspoke192.168.1.12엔드 엔티티 (End Entity)

CA는 별도의 호스트로 분리해도 되지만, 개인 메쉬라면 상시 가동되는 hub에 함께 두는 것이 현실적입니다. 본 기사에서는 desktop을 CA 호스트 겸 hub로 진행합니다.

대략적인 흐름

  • CA 호스트(desktop)에서 CA를 생성한다
  • 각 호스트에서 인증서를 발행한다 (desktop 자신도 포함)
  • init --mode hub/spoke --tlszenohd 설정을 mTLS 버전으로 전환한다

Step 1: CA 만들기 (desktop)

CA 키는 desktop 내부에만 두고 외부로 유출하지 않습니다.

# [on desktop / 192.168.1.10]
kioku-mesh tls init-ca

이로써 CA 키와 CA 인증서가 desktop에 생성됩니다. 이후 단계의 tls sign이 이 CA 키를 사용하여 피어(Peer) 인증서에 서명하게 됩니다.

Step 2: 각 호스트의 인증서 준비하기

여기에는 두 가지 흐름이 있습니다.

  • A. SSH가 있는 흐름: 각 spoke에서 desktop으로 SSH가 가능하다면 enroll 한 번으로 해결
  • B. SSH가 없는 흐름: requestsigninstall 과정을 수동으로 복사(paste)하여 전달

어느 쪽을 선택하든 비밀키는 해당 호스트 밖으로 나가지 않습니다 (이동하는 것은 CSR과 서명된 인증서뿐입니다).

desktop 자신 (hub & CA 호스트)

CA 호스트가 자기 자신에게 서명하는 형태가 됩니다.

# [on desktop / 192.168.1.10]
kioku-mesh tls request --san 192.168.1.10 --san 127.0.0.1 -o /tmp/desktop.csr
kioku-mesh tls sign /tmp/desktop.csr -o /tmp/desktop.bundle
...
  • --san에는 다른 피어(Peer)가 접속(dial)해올 대상 주소를 모두 넣습니다 (LAN IP, Tailscale IP, 필요하다면 127.0.0.1)
  • install 후, desktop에는 CA 인증서와 자신을 위한 피어(Peer) 인증서 + 비밀키가 갖춰집니다.

laptop (enroll 사용)

laptop (SSH가 있는 경우: laptop에서 desktop으로 SSH가 가능하다는 전제라면, 이것만 하면 됩니다.)

# [on laptop / 192.168.1.11]
kioku-mesh tls enroll user@192.168.1.10 --san 192.168.1.11

enroll 명령은 내부적으로 "CSR 생성 → SSH를 통한 tls sign 실행 → 반환된 bundle을 install" 과정을 한 번에 수행합니다. 완료 후, laptop에는 CA 인증서와 본인용 peer 인증서 + 비밀키가 모두 갖춰집니다.

SSH 포트나 추가 옵션은 --ssh-port / --ssh-opt (복수 지정 가능)로 지정할 수 있습니다.

agent-server (SSH 미사용: copy-paste flow)

SSH를 사용하고 싶지 않거나 사용할 수 없는 호스트는, request로 출력된 CSR 블록을 desktop의 tls sign에 붙여넣고, 반환된 bundle을 tls install에 붙여넣습니다.

# [on agent-server / 192.168.1.12]
kioku-mesh tls request --san 192.168.1.12

출력된 CSR 블록을 desktop으로 가져갑니다 (클립보드, Slack DM 등 무엇이든 상관없습니다. CSR은 비밀 정보가 아닙니다).

# [on desktop / 192.168.1.10]
kioku-mesh tls sign
# 프롬프트에 따라 CSR을 붙여넣기 → 서명된 bundle이 출력됨

반환된 bundle을 agent-server로 다시 가져와서 install 합니다.

# [on agent-server / 192.168.1.12]
kioku-mesh tls install
# 프롬프트에 따라 bundle을 붙여넣기

비밀키는 agent-server에서 단 한 발짝도 움직이지 않았다는 점에 주목하세요. 복사/붙여넣기로 움직이는 것은 CSR과 서명된 인증서뿐입니다.

--san 선택 방법

SAN (Subject Alternative Name)에는 "이 피어(peer)가 다른 피어로부터 dial 될 때의 주소"를 전부 넣습니다.

  • LAN IP는 필수입니다 (예: 192.168.1.11). Tailscale이나 Wireguard를 통해서도 도달하게 하려면 해당 주소도 포함합니다.
  • desktop (hub)은 모든 spoke로부터 dial 되므로, 특히 SAN을 포괄적으로 설정해야 합니다.

호스트 이름으로 연결한다면 hostname도 --san에 추가할 수 있습니다.

zenohd 설정을 mTLS 버전으로 전환하기

Step 3: 여기까지 각 호스트에 인증서가 준비되었다면, init--tls를 추가하여 재생성하기만 하면 됩니다. 제4회의 init 명령에 플래그를 하나 추가하는 형태가 됩니다.

desktop (hub)

# [on desktop / 192.168.1.10]
kioku-mesh init --mode hub \
--listen 127.0.0.1 \
...

--tls를 붙이면:

  • 크로스 호스트(cross-host)의 endpoint가 tcp/...에서 tls/...로 전환됩니다.
  • transport.link.tls 블록이 작성되며, 로컬의 인증서 저장소(certificate store)를 참조합니다.
  • enable_mtls: true가 되어 상대 피어에게도 인증서 제시를 요구합니다.
  • 루프백 (127.0.0.1)은 tcp/ 상태로 평문(plaintext) 통신을 유지합니다.

루프백을 평문으로 남겨두는 것은 의도적인 설계입니다. 동일 호스트 상의 CLI / MCP 클라이언트는 TCP/loopback으로 접속할 수 있으면 충분하며, 여기에 TLS를 강제하면 kioku-mesh save 명령 한 번을 위해 클라이언트에게도 인증서를 배포해야 하는 상황이 발생합니다. 신뢰 경계(trust boundary)를 호스트 경계와 일치시키겠다는 판단으로 이해해 주세요.

laptop / agent-server (spoke)

# [on laptop / 192.168.1.11]
kioku-mesh init --mode spoke \
--listen 127.0.0.1 \
...
# [on agent-server / 192.168.1.12]
kioku-mesh init --mode spoke \
--listen 127.0.0.1 \
...

--connect 192.168.1.10은 그대로 두어도 괜찮습니다. init --tls

크로스 호스트(cross-host)의 endpoint만 tls/192.168.1.10:7447에 상당하는 값으로 다시 작성해 줍니다 (루프백(loopback)은 tcp/ 그대로 유지됩니다).

zenohd 재시작

각 호스트에서 zenohd를 재시작하여 새로운 설정을 불러옵니다.

# [on each host]
systemctl --user restart kioku-mesh-zenohd.service # systemd 상주 서비스인 경우
# 또는 수동 실행인 경우 zenohd 프로세스를 kill 하고 재시작

동작 확인

제4회와 동일한 save / search 왕복 과정을 다시 수행합니다.

# [on desktop / 192.168.1.10]
kioku-mesh save "mTLS를 통한 테스트 (desktop 발)" --memory-type note --subject mtls-test
# [on laptop / 192.168.1.11]
kioku-mesh search "desktop 발"

성공한다면 mTLS 메쉬(mesh)가 살아있는 것입니다. 역방향도 확인해 둡시다.

# [on laptop / 192.168.1.11]
kioku-mesh save "답장 (laptop 발)" --memory-type note --subject mtls-test
# [on desktop / 192.168.1.10]
...

doctor 또한 mTLS를 의식한 출력으로 나타납니다.

kioku-mesh doctor

인증서의 유효 기간이나 CA / peer cert의 내용을 확인하고 싶을 때는:

kioku-mesh tls info

실패 패턴 해결 방법

증상원인 및 대처
spoke에서 hub로 연결되지 않음hub의 peer 인증서 SAN에 spoke가 dial하는 주소가 포함되지 않음 → desktop에서 tls request --san 192.168.1.10 --san <다른 주소>부터 install까지 다시 수행
동일 호스트의 CLI에서 연결되지 않음--tls 사용 시 --listen127.0.0.1을 넣는 것을 잊음 → init --tls --listen 127.0.0.1 ...로 재생성
불필요한 UDP endpoint가 있음mTLS는 TCP 위에서 동작함 → UDP의 listen / connect는 제거하거나, tcp/인 상태로 운용
인증서 만료kioku-mesh tls info로 기한 확인 → enroll (SSH 사용 시) 또는 request / sign / install (copy-paste)로 재발급

인증서 로테이션 (Certificate Rotation)

만료 기한은 tls init-ca 시점에 기본값으로 설정됩니다. 기한이 다가오면 각 피어(peer)에서 enroll 또는 copy-paste flow를 재실행하는 것만으로도 다음과 같은 작업이 가능합니다.

  • 오래된 인증서 파일이 새로운 파일로 교체됨
  • zenohd를 재시작하면 새로운 인증서로 연결됨

CA를 다시 만들고 싶다면 tls init-ca를 다시 수행하고, 각 피어에서 재 enroll 합니다 (CA가 변경되므로 모든 피어의 재발급이 필요합니다).

상세한 운용이나 트러스트 모델(trust model)에 관한 논의는 리포지토리 내의 docs/mtls.md를 참조하십시오.

연재를 마치며

여기까지 kioku-mesh의 주요 요소들을 한 차례 모두 다루었습니다.

  • 제1회: 컨셉과 아키텍처 (Architecture)
  • 제2회: local 모드와 MCP 클라이언트 연동 (도구 간 연동)
  • 제3회: Zenoh + RocksDB + SQLite index의 역할
  • 제4회: hub와 spoke로 메쉬를 구성 (머신 간 연동)
  • 제5회 (본 기사): mTLS로 마무리

"자체 peer-to-peer", "로컬 퍼스트 (Local-first)", "MCP 네이티브"라는 컨셉을, 수중의 몇 대 기기로 그대로 재현할 수 있는 상태에 도달했을 것입니다. 이제 평소의 개발 과정에 kioku-mesh를 섞어 나가기만 하면, 에이전트 간·머신 간의 기억이 점점 더 재사용될 것입니다.

실운용에서 발견된 팁(tips), ADR(Architecture Decision Records), 운용상의 함정 등은 리포지토리(repository) 측에 쌓아 나갈 예정이므로, docs/Spec.mddocs/adr/도 가끔씩 살펴보시기 바랍니다.

참고 링크

  • 리포지토리 내부:
    docs/mtls.md
    (신뢰 모델(Trust Model) / 상세 절차 / 로테이션(Rotation))

  • 리포지토리 내부:
    config/peers/example_5peer.md
    (5피어(5-peer) 구성, --tls로의 전환 기술 포함)

  • Zenoh 공식: TLS / mTLS

  • 연재 제1회: kioku-mesh란 무엇인가

  • 연재 제2회: kioku-mesh를 Claude Code와 Codex CLI에 MCP로서 연결하기

  • 연재 제3회: kioku-mesh의 내부 이해 — Zenoh와 RocksDB와 SQLite index

  • 연재 제4회: kioku-mesh로 hub를 세우고 spoke를 연결하여, 머신을 가로지르는 공유 메모리 만들기

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0