본문 바로가기
개발/바이브 코딩

Hermes 원격 agent를 로컬 맥북에 remote gateway로 연결하기

by Lajancia 2026. 7. 11.
728x90
반응형

목차

  1. 이 가이드가 필요한 이유
  2. 아키텍처 개요
  3. 사전 준비사항
  4. 1단계: Tailscale 설치 및 설정
  5. 2단계: 현재 상태 파악
  6. 3단계: 위험 — 0.0.0.0 바인드
  7. 4단계: Tailscale IP에 바인드 (해결)
  8. 5단계: 인증 설정
  9. 6단계: Desktop App에서 연결
  10. 심층 분석: Tailscale 네트워킹의 원리
  11. 대안 비교
  12. 보안 강화 체크리스트
  13. 문제 해결
  14. 결론

이 가이드가 필요한 이유

Hermes Agent는 백엔드 서버로서 지속적으로 실행되도록 설계되었습니다. hermes serve 명령어는 Desktop App과 원격 백엔드에 WebSocket/JSON-RPC 방식으로 전원을 공급합니다. 이로 인해 다음과 같은 일반적인 시나리오가 발생합니다:

  • 서버 머신(예: Mac Mini, Linux VPS): 24시간 hermes serve 실행
  • 클라이언트 머신(예: MacBook Pro): Hermes Desktop App 실행 후 원격 서버에 연결

가장 쉬운 방법은 0.0.0.0(모든 인터페이스)에 바인드하는 것이지만, 이는 공인 인터넷, 로컬 LAN, 접속 중인 모든 VPN에 백엔드를 노출시킵니다. 방화벽 규칙 하나 잘못 설정하거나 백엔드에 제로데이 취약점이 발견되면 전체 Hermes 세션이 노출될 수 있습니다.

이 가이드는 Tailscale(제로 설정 WireGuard 메시 VPN)과 직접 IP 바인딩 및 인증을 결합한 심층 방어(defense-in-depth) 접근법을 제시합니다.


아키텍처 개요

┌─────────────────────────────────────────────────────────────────────┐
│                      Tailnet (사설 메시 네트워크)                     │
│  ┌──────────────────────┐              ┌──────────────────────────┐│
│  │  MacBook Pro (클라이언트)│              │    Mac Mini (서버)       ││
│  │                      │              │                          ││
│  │  Hermes Desktop App  │              │  hermes serve --host     ││
│  │       │              │  WireGuard   │   <Tailscale IP>:9119    ││
│  │       ▼              │◄────────────►│       │                  ││
│  │  Gateway 설정         │  암호화 터널   │       ▼                  ││
│  │  URL: http://host-   │              │  Dashboard 인증          ││
│  │       name.ts.net:   │              │  username: admin         ││
│  │       9119           │              │  password: (해싱됨)      ││
│  └──────────────────────┘              └──────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘
         ❌ 공인 인터넷              ❌ 로컬 LAN (192.168.x.x)
         ❌ 카페 WiFi               ❌ 기타 모든 네트워크

핵심 속성: Tailscale IP(100.x.x.x)는 Tailscale WireGuard 메시 내에서만 존재합니다. 다른 어떤 네트워크도 이 IP로 라우팅할 수 없습니다. Hermes가 이 IP에 바인드되면 Tailnet 외부에서는 물리적으로 접근이 불가능합니다.


사전 준비사항

요구사항 비고
Tailscale 계정 무료 플랜: 최대 3명의 사용자, 100대의 기기
양쪽 기기에 Tailscale 설치 macOS: brew install --cask tailscale / Linux: curl 스크립트
MagicDNS 활성화 설정 → DNS → Enable MagicDNS (Tailscale 관리 콘솔)
두 기기가 같은 tailnet에 로그인 각각 tailscale up 실행, 같은 계정으로 인증
서버에 Hermes Agent 설치 pip install hermes-agent 또는 인스톨러 사용
클라이언트에 Hermes Desktop App Nous Research에서 제공

연결 확인:

# 양쪽 기기에서 실행
tailscale status
# 예상 출력 - 두 기기가 같은 계정 아래 표시되어야 함
100.x.x.x     my-server     user@  macOS  -
100.y.y.y     my-laptop     user@  macOS  -

1단계: Tailscale 설치 및 설정

Tailscale이 아직 설정되지 않은 경우:

# macOS (Homebrew)
brew install --cask tailscale

# 앱 실행 (Applications 폴더에서 열고 브라우저로 로그인)
open -a Tailscale

# 또는 CLI 데몬 실행 (헤드리스)
sudo tailscaled --tun=userspace-networking &
tailscale up

MagicDNS 활성화 (호스트명 기반 URL에 필수):

  1. Tailscale 관리 콘솔 열기
  2. Enable MagicDNS 클릭
  3. tailnet 이름 확인 (예: tailnet1234.ts.net)

DNS 확인:

dig <서버-호스트명>.<tailnet-이름>.ts.net +short
# 예상: 100.x.x.x (서버의 Tailscale IP)

2단계: 현재 상태 파악

변경 전에 현재 실행 중인 상태를 확인합니다:

# 모든 Hermes 프로세스 찾기
ps aux | grep hermes | grep -v grep

# 모든 인터페이스에서 리스닝 중인 포트 확인
lsof -iTCP -sTCP:LISTEN -P -n | grep "0.0.0.0"

다음과 같은 출력이 보일 수 있습니다:

python3.1  12345  Ghost  14u  IPv4  0x...  TCP *:9119 (LISTEN)   ❌ 0.0.0.0
python3.1  12345  Ghost  26u  IPv4  0x...  TCP *:8080 (LISTEN)   ❌ 0.0.0.0

둘 다 위험 신호입니다. 이제부터 고쳐봅시다.


3단계: 위험 — 0.0.0.0 바인드

프로세스가 0.0.0.0에 바인드되면 사용 가능한 모든 네트워크 인터페이스에서 연결을 수락합니다:

┌──────────────────────────────────────────┐
│              Mac Mini                     │
│                                          │
│  ┌─────┐  en0 (WiFi) ─── 192.168.1.100  │
│  │Hermes├── en1 (유선) ──── 10.0.0.5    │
│  │:9119 ├── utun (Tailscale) ─ 100.x.x.x│
│  └─────┘  lo0 (루프백) ── 127.0.0.1    │
│                                          │
│  바인드: 0.0.0.0 → 모든 인터페이스 ❌   │
└──────────────────────────────────────────┘

위험: 같은 WiFi(카페, 코워킹 스페이스)에 있는 사람, LAN에 있는 사람, 그리고 라우터에 포트 포워딩이 설정되어 있다면 공인 인터넷상의任何人都 연결을 시도할 수 있습니다.


4단계: Tailscale IP에 바인드 (해결)

Tailscale IP가 안전한 이유:

Tailscale은 각 기기에 사설 CGNAT 범위 100.64.0.0/10의 IP를 할당합니다. 이 범위는:

  • 공인 인터넷에서 라우팅 불가 (RFC 6598)
  • 로컬 LAN에서 라우팅 불가 (다른 서브넷)
  • Tailscale WireGuard 터널을 통해서만 접근 가능
  • 자동으로 종단 간 암호화

명령어:

# 서버의 Tailscale IP 확인
TAILSCALE_IP=$(tailscale ip -4)
echo "서버 Tailscale IP: $TAILSCALE_IP"

# 실행 중인 Hermes serve 중지
pkill -f "hermes serve" 2>/dev/null

# Tailscale IP에만 바인드하여 Hermes serve 시작
hermes serve --host $TAILSCALE_IP --port 9119

바인드 확인:

lsof -i :9119 -P -n
# 예상: TCP 100.x.x.x:9119 (LISTEN) ✅

수정 후 상태:

┌──────────────────────────────────────────┐
│              Mac Mini                     │
│                                          │
│  ┌─────┐  en0 (WiFi) ─── 192.168.1.100  │
│  │Hermes├── en1 (유선) ──── 10.0.0.5    │
│  │:9119 ├── utun (Tailscale) ─ 100.x.x.x│ ← 이것만
│  └─────┘  lo0 (루프백) ── 127.0.0.1    │
│                                          │
│  바인드: 100.x.x.x → Tailscale 전용 ✅  │
└──────────────────────────────────────────┘

launchd로 영구 설정:

# launchd plist 생성
cat > ~/Library/LaunchAgents/hermes.serve.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>hermes.serve</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/hermes</string>
        <string>serve</string>
        <string>--host</string>
        <string>$TAILSCALE_IP</string>
        <string>--port</string>
        <string>9119</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
EOF

launchctl load ~/Library/LaunchAgents/hermes.serve.plist

참고: Tailscale IP가 재부팅 시 변경된다면, 설정에서 MagicDNS 호스트명을 사용하거나 Tailscale 관리 콘솔에서 고정 IP를 예약하세요.


5단계: 인증 설정

Tailscale IP에 바인드하는 것은 무단 네트워크 접근을 차단하지만, tailnet 내의 모든 기기는 여전히 포트에 접근할 수 있습니다. 두 번째 방어선으로 사용자명/비밀번호 인증을 추가합니다:

# ~/.hermes/config.yaml
dashboard:
  basic_auth:
    username: admin
    password: <강력한_비밀번호>

인증 흐름 (소스 코드: plugins/dashboard_auth/basic/__init__.py):

┌─────────┐     POST /auth/password-login      ┌──────────┐
│ 브라우저 │ ──── { username, password } ──────→ │  서버   │
│         │                                      │          │
│         │ ←──── 302 (쿠키 설정) ──────────── │ (해시    │
│         │                                      │  검증)   │
└─────────┘                                      └──────────┘

코드 로직 (단순화):

# 우선순위:
# 1. 환경변수 HERMES_DASHBOARD_BASIC_AUTH_PASSWORD → 메모리에서 해싱
# 2. 설정 파일의 password_hash                     → 직접 사용
# 3. 설정 파일의 password (평문)                   → 메모리에서 해싱

if 환경변수_비밀번호:
    password_hash = hash_password(환경변수_비밀번호)      # 환경변수 우선
elif 설정_password_hash:
    password_hash = 설정_password_hash                    # 해시가 평문보다 우선
elif 설정_비밀번호:
    password_hash = hash_password(설정_비밀번호)          # 평문을 메모리에서 해싱

중요: password_hashpassword가 모두 설정 파일에 있으면 password_hash가 우선시되어 password무시됩니다. 제거하거나 삭제하세요:

# password_hash 라인 제거
sed -i '' '/password_hash:/d' ~/.hermes/config.yaml

대안 — 환경변수 사용 (설정 파일에 평문 저장 안 함):

export HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=<강력한_비밀번호>

영구 저장하려면 .env 파일이나 셸 프로파일에 추가하세요.

강력한 비밀번호 생성:

openssl rand -base64 24
# 예시 출력: Xk9pLm3qRt7vWz1yBn4cFg8h

적용을 위해 재시작:

pkill -f "hermes serve"
hermes serve --host $(tailscale ip -4) --port 9119

6단계: Desktop App에서 연결

클라이언트 머신(MacBook Pro)에서:

  1. Hermes Desktop App 실행
  2. 설정 → Gateway로 이동
  3. "Add Remote Gateway" 클릭
  4. 입력:
    필드
    Connection URL http://<서버-호스트명>.<tailnet>.ts.net:9119
    Username admin
    Password 설정한 비밀번호
  5. "Connect" 클릭

서버의 MagicDNS 호스트명 찾기:

tailscale status
# 서버의 호스트명 확인

# 또는 프로그램으로 가져오기:
tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//'
# 출력: my-server.tailnet1234.ts.net

연결 확인:

# 클라이언트 머신에서
curl -s -o /dev/null -w "%{http_code}" http://<서버-호스트명>.<tailnet>.ts.net:9119
# 예상: 302 (/login으로 리다이렉트) ✅

# 전체 응답 확인
curl -s http://<서버-호스트명>.<tailnet>.ts.net:9119 | head -5
# 예상: <!doctype html>... (로그인 페이지) ✅

심층 분석: Tailscale 네트워킹의 원리

이 접근법이 안전한 이유를 이해하려면 Tailscale의 아키텍처를 알아야 합니다:

┌─────────────────────────────────────────────────────────────────┐
│                        Tailscale 개요                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐      ┌─────────────────┐      ┌──────────┐       │
│  │ Mac Mini │      │ 조정 서버       │      │ MacBook  │       │
│  │ 100.x.x.x│◄────►│ (NAT 트래버설)  │◄────►│ 100.y.y.y│       │
│  │          │      │                 │      │          │       │
│  └────┬─────┘      └─────────────────┘      └────┬─────┘       │
│       │                                          │              │
│       │           직접 WireGuard                  │              │
│       └─────────────── UDP 터널 ─────────────────┘              │
│                                                                  │
│  암호화: WireGuard (Noise Protocol)                              │
│  인증: 노드 키 (curve25519) + SSO 로그인                         │
│  IP 할당: CGNAT 범위 100.64.0.0/10                              │
│  DNS: MagicDNS (*.ts.net) 선택 사항                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

핵심 보안 속성:

  1. 열린 포트 없음: Tailscale은 NAT/방화벽을 통해 아웃바운드 전용 연결을 사용합니다. 포트 포워딩이 필요 없습니다.
  2. 종단 간 암호화: 기기 간 트래픽은 WireGuard의 Noise 프로토콜로 암호화됩니다. Tailscale의 조정 서버조차 복호화할 수 없습니다.
  3. 완벽한 순방향 비밀성: 세션 키는 일회용입니다.
  4. 노드 신원: 각 기기는 Curve25519 키 쌍으로 인증하며, 조정 서버를 통해 검증됩니다.
  5. 공인 IP에서 리스닝 없음: Hermes 서버는 절대 공인 또는 LAN 주소에 바인드되지 않습니다.

대안 비교

접근법 보안 복잡도 URL 형식 추천 대상
Tailscale IP 직접 (이 가이드) ✅ 높음 낮음 http://host.ts.net:9119 대부분의 사용자
127.0.0.1 + Tailscale Serve ✅ 높음 중간 https://host.ts.net HTTPS 필수 환경
127.0.0.1 + SSH 터널 ✅ 높음 높음 http://localhost:9119 Tailscale 없는 환경
0.0.0.0 + 방화벽 규칙 ❌ 중간 높음 http://공인IP:9119 비추천
Cloudflare Tunnel ✅ 높음 중간 https://host.domain.com 공개 호스트명 필요

"127.0.0.1 + Tailscale Serve"를 첫 번째 선택으로 하지 않는 이유:

tailscale serve는 포트 443에 HTTPS 리버스 프록시를 생성하여 로컬 포트로 전달합니다:

tailscale serve --bg 9119
# 결과: https://host.ts.net/ → http://127.0.0.1:9119

동작은 하지만:

  • 클라이언트는 https://(포트 443)만 사용 가능, 포트 9119에 직접 접근 불가
  • Desktop App의 "Port" 필드는 직접 백엔드 포트를 기대함
  • Tailscale의 WireGuard 터널로 이미 암호화된 연결에 불필요한 TLS 종료 홉 추가
  • Tailscale이 관리하는 TLS 인증서(Let's Encrypt)로 인한 복잡성 증가

직접 Tailscale IP 바인딩이 더 간단하고 투명합니다.


보안 강화 체크리스트

  • hermes serve가 Tailscale IP에 바인드됨 (0.0.0.0 아님)
  • ✅ 모든 0.0.0.0 서비스 마이그레이션 또는 보안 조치 완료
  • ✅ Dashboard 인증 활성화 (username + password)
  • password_hash 설정에서 제거 (평문 비밀번호 사용 시)
  • ✅ 방화벽 확인: lsof -iTCP -sTCP:LISTEN -P -n | grep "0.0.0.0" 결과 없음
  • ✅ Tailscale ACL 검토 (선택): 포트 9119에 접근 가능한 tailnet 사용자 제한 고려
  • ✅ 자동 시작 설정 (launchd/systemd) 완료
  • ✅ 강력한 비밀번호 사용 (openssl rand -base64 24로 생성)
  • ✅ 두 기기 모두 같은 tailnet에 연결됨

문제 해결

문제 1: Desktop App에서 "서버에 연결할 수 없음"

네트워크 연결 확인:

# 클라이언트 머신에서
tailscale ping <서버-tailscale-ip>
# 예상: pong from <서버> via <직접/릴레이> in <ms>

# DNS 확인
dig <서버-호스트명>.<tailnet>.ts.net +short
# 예상: 100.x.x.x

# 포트 연결 확인
nc -zv <서버-tailscale-ip> 9119
# 예상: Connected to ...

서버 프로세스 확인:

# 서버 머신에서
lsof -i :9119 -P -n
# 예상: TCP 100.x.x.x:9119 (LISTEN)

curl -s http://localhost:9119
# 예상: {"error":"Headless backend..."} 또는 HTML 로그인 페이지

문제 2: 로그인 시 "잘못된 자격 증명"

# 현재 설정 확인
grep -A 5 "basic_auth" ~/.hermes/config.yaml

# password_hash가 있다면 제거
sed -i '' '/password_hash:/d' ~/.hermes/config.yaml

# Hermes serve 재시작
pkill -f "hermes serve"
hermes serve --host $(tailscale ip -4) --port 9119

문제 3: Tailscale IP가 재부팅 후 변경됨

# 옵션 A: Tailscale 관리 콘솔에서 고정 IP 예약
# https://login.tailscale.com/admin/machines 접속
# 서버 클릭 → "Reserve IP"

# 옵션 B: 스크립트에서 MagicDNS 호스트명 사용
HOSTNAME=$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')
# IP 대신 $HOSTNAME 사용

문제 4: tailscale 명령어를 찾을 수 없음

# macOS Homebrew
PATH="/opt/homebrew/bin:$PATH"

# Linux
PATH="/usr/bin:$PATH"

# 셸 프로파일에 추가하여 영구 설정
echo 'export PATH="$PATH:/opt/homebrew/bin"' >> ~/.zshrc

문제 5: hermes serve가 즉시 종료됨

# 로그 확인
hermes logs

# 다른 프로세스가 같은 포트를 사용 중인지 확인
lsof -i :9119 -P -n

# 포그라운드에서 실행하여 오류 확인
hermes serve --host $(tailscale ip -4) --port 9119

결론

Hermes Serve를 0.0.0.0 대신 Tailscale IP에 바인드하면 다음과 같은 이점을 얻을 수 있습니다:

  1. 심층 방어: 네트워크 격리(Tailscale IP) + 애플리케이션 계층 인증(dashboard 비밀번호)
  2. 공개 노출 제로: 공인/LAN 인터페이스에 열린 포트 없음
  3. 종단 간 암호화: 로컬 트래픽도 WireGuard 터널
  4. 최소 복잡도: 리버스 프록시, TLS 설정, 방화벽 규칙 불필요
  5. 포트 보존: 프록시 리디렉션 없이 포트 9119에 직접 연결

이 패턴 — 가상 네트워크 인터페이스 IP에 바인드, 0.0.0.0이 아닌 — 은 데이터베이스, 개발 서버, API 등 Tailscale을 통해 안전하게 노출하려는 모든 서비스에 적용할 수 있습니다.


Guide by tara. All IPs, passwords, and tokens shown are examples only. Replace with your own values. This guide is for educational purposes — always follow the principle of least privilege.
가이드 작성: tara. 표시된 모든 IP, 비밀번호, 토큰은 예시입니다. 실제 환경에 맞게 값을 교체하세요. 이 가이드는 교육 목적으로 제공되며, 항상 최소 권한 원칙을 따르세요.

반응형

'개발 > 바이브 코딩' 카테고리의 다른 글

Openclaw + Ollama 사용기 - 2  (0) 2026.04.05
Openclaw + Ollama 사용기 - 1  (0) 2026.03.22
Ollama 모델 튜닝  (0) 2026.03.07
Ollama + MCP Server 붙이기 - 3  (5) 2025.07.27
Ollama + MCP Server 붙이기 - 2  (9) 2025.07.20