RUNBOOK Sigil 배포 / 테스트

# 2-머신 테스트 + 배포 네트워크·포트 가이드

구성: Mac (agent + sender) ↔ Rocky/RHEL VM (bridged, sigil-server) · 같은 LAN 서브넷 · 날짜: 2026-05-25 · 예시 IP: VM = `192.168.0.50` (본인 값으로 치환)

## 1. 토폴로지 & 데이터 흐름

    flowchart LR
      subgraph mac["Mac — agent 호스트 · inbound 포트 없음"]
        direction TB
        agent["sigil (agent)"]
        spool[("JSONL spool")]
        sender["sigil-sender"]
        agent -->|"watch·hash·JSONL"| spool
        spool -->|reads| sender
        sender -.->|"apply_policy (UDS·로컬)"| agent
      end
      subgraph vm["Rocky VM · bridged"]
        server["sigil-server :8443"]
        store[("events_out_dir · host_id별 JSONL")]
        server --> store
      end
      sender ==>|"mTLS 8443 · 이벤트 push"| server
      server -.->|"mTLS 8443 · 서명정책 pull (ETag)"| sender
      classDef comp fill:#d1fae5,stroke:#059669,color:#064e3b;
      class agent,spool,sender,server comp;

- agent 는 바깥으로 안 나간다. 네트워크 송신은 `sigil-sender` 가 전담 → server 로 outbound.
- agent ↔ sender 는 같은 호스트의 Unix domain socket(control.sock). 네트워크 포트 불필요.
- 서버는 단일 포트(8443) 에 모든 `/v1/*` 엔드포인트 (data + control + read API 동일 bind).

## 2. 포트 / 방화벽 표 (실무에서 열어야 할 것)

컴포넌트 (호스트) | 방향 | 포트 | 프로토콜 | 용도 | 상대
--- | --- | --- | --- | --- | ---
sigil-server (서버 VM) | inbound | 8443/tcp (`bind` 설정값) | TLS (mTLS) | `POST /v1/events` 수신 · `GET /v1/policy` 서빙 · `/v1/healthz` · `/v1/meta`+read API — 전부 같은 포트 | agent 호스트들, (옵션) sigil-manager / 운영자
agent 호스트 (Mac/엔드포인트) | outbound | 8443/tcp | TLS | sigil-sender → server (이벤트 push + 정책 pull) | → sigil-server
agent 호스트 | outbound | 443/tcp 또는 SIEM 포트 (예: Splunk HEC 8088) | TLS | sigil-sender → SIEM 직접 경로 쓸 때만 | → SIEM
agent ↔ sender | 로컬 | 네트워크 포트 없음 | Unix socket | apply_policy IPC (같은 호스트) | n/a
(옵션) sigil-manager | outbound | 8443/tcp | TLS+bearer | server read API(`/v1/meta`, fleet) 조회 | → sigil-server
(옵션) sigil-manager | inbound | 자체 웹 포트 (예: 8080) | TLS | fleet 웹 콘솔 | 운영자 브라우저

요점. ① 서버만 inbound 1개(8443) 열면 끝. ② agent 호스트는 inbound 0개 — 리슨 포트 없음, outbound 만 (lockdown 된 엔드포인트 fleet 에 유리). ③ agent↔sender 는 로컬 UDS 라 방화벽 무관.

인증 계층 구분: data/control 경로(`/v1/events`, `/v1/policy`)는 mTLS 클라이언트 cert 로 인증. read API(`/v1/meta`, fleet)는 그 위에 bearer 토큰 게이트(Phase 3b.4) — 토큰 미설정이면 404. `/v1/healthz` 만 인증 없음(단 여전히 mTLS 핸드셰이크는 필요).

## 3. PKI 한 번 생성 (아무 박스, 예: Mac)

데모 `demo/init.sh` 의 openssl 묶음 그대로, server cert 의 SAN 만 조정.

    # CA
    openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
      -keyout ca.key -out ca.crt -subj "/CN=sigil-test-ca" \
      -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"

    # 서버 cert — SAN 에 hostname(권장) + IP. sender 가 접속할 주소가 SAN 에 있어야 함.
    cat > server.ext <<'EOF'
    basicConstraints = CA:FALSE
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth
    subjectAltName = DNS:sigil-server, IP:192.168.0.50
    EOF
    openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=sigil-server"
    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 \
      -extfile server.ext -out server.crt

    # 클라이언트(sender) cert
    cat > client.ext <<'EOF'
    basicConstraints = CA:FALSE
    keyUsage = critical, digitalSignature
    extendedKeyUsage = clientAuth
    EOF
    openssl req -new -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=mac-sender"
    openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 \
      -extfile client.ext -out client.crt

정책 서명 키 + keystore + signed bundle (control-plane 용; data-plane 만 볼 거면 생략 가능):

    sigil-sign keygen --id test-key --out signing-key.json
    PUB=$(jq -r .ed25519_pubkey_b64 signing-key.json)
    cat > policy-signing-pubkeys.pem <<EOF
    { "pubkeys": [ { "id":"test-key","ed25519_pubkey_b64":"$PUB","valid_from":"2020-01-01T00:00:00Z","valid_until":"2099-01-01T00:00:00Z" } ] }
    EOF
    cp config/policy.example.yaml policy.yaml
    sigil-sign sign --in policy.yaml --key signing-key.json \
      --policy-version 2 --valid-until 2099-01-01T00:00:00Z --out signed-policy.json

## 4. VM (Rocky aarch64, bridged) — sigil-server

ARM64 주의. Apple Silicon Mac 의 VM 은 aarch64. prebuilt/x86_64 rpm 은 안 됨(cross-arch 패키징 미구현 — open issue #12). VM 에서 소스 빌드가 정답.

    # Rocky aarch64 — 소스 빌드
    sudo dnf install -y gcc git
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh && source "$HOME/.cargo/env"
    git clone https://github.com/Ju571nK/sigil && cd sigil
    cargo build --release -p sigil-server         # → target/release/sigil-server (네이티브 aarch64)
    sudo mkdir -p /etc/sigil-server /var/lib/sigil-server/events
    # A에서 만든 것 전송: server.crt server.key ca.crt signed-policy.json
    sudo cp server.crt server.key ca.crt signed-policy.json /etc/sigil-server/

`/etc/sigil-server/server.yaml`:

    bind: "0.0.0.0:8443"
    events_out_dir: "/var/lib/sigil-server/events"
    policy_bundle_path: "/etc/sigil-server/signed-policy.json"
    tls_cert_path: "/etc/sigil-server/server.crt"
    tls_key_path: "/etc/sigil-server/server.key"
    client_ca_path: "/etc/sigil-server/ca.crt"     # ← 3개 다 켜야 mTLS ON (안 켜면 PLAIN HTTP 경고)

    # 방화벽 (Rocky/firewalld)
    sudo firewall-cmd --add-port=8443/tcp --permanent && sudo firewall-cmd --reload
    # SELinux enforcing 면: 8443 은 보통 http_port_t 에 포함. 커스텀 포트 쓰면:
    #   sudo semanage port -a -t http_port_t -p tcp <port>
    # 실행
    ./target/release/sigil-server serve --config /etc/sigil-server/server.yaml
    ip addr | grep 192.168          # VM 의 LAN IP 확인 (bridged 면 LAN 대역)

## 5. Mac — agent + sender

    echo "192.168.0.50  sigil-server" | sudo tee -a /etc/hosts   # hostname SAN 매핑
    mkdir -p ~/sigil-test/{events,state,run}
    # 소스 빌드 (Apple Silicon = aarch64-apple-darwin 네이티브)
    cargo build --release -p sigil-agent -p sigil-sender
    # A에서: ca.crt client.crt client.key policy-signing-pubkeys.pem policy.yaml 가져오기

`~/sigil-test/sender.yaml`:

    server_base_url: "https://sigil-server:8443"
    client_cert_path: "/Users/ju571nk3n/sigil-test/client.crt"
    client_key_path:  "/Users/ju571nk3n/sigil-test/client.key"
    server_ca_path:   "/Users/ju571nk3n/sigil-test/ca.crt"
    events_dir:       "/Users/ju571nk3n/sigil-test/events"
    offset_path:      "/Users/ju571nk3n/sigil-test/state/sender-offset.json"
    agent_control:    "/Users/ju571nk3n/sigil-test/run/control.sock"
    dead_letter_dir:  "/Users/ju571nk3n/sigil-test/events/dead-letter"
    policy_poll_interval: 10

    # 터미널 1 — agent (events-dir / control 경로를 sender.yaml 과 일치)
    ./target/release/sigil run --policy ~/sigil-test/policy.yaml \
      --events-dir ~/sigil-test/events --state-db ~/sigil-test/state/state.db
    # 터미널 2 — sender
    ./target/release/sigil-sender --config ~/sigil-test/sender.yaml start

경로 정합이 핵심. sender 의 `events_dir`/`agent_control` 는 Mac agent 의 실제 events·control 경로와 같아야 함. agent 의 control socket override 플래그가 없으면 `sigil show config` 로 실제 경로를 확인하고 sender.yaml 을 맞추세요.

## 6. 검증

    # Mac → VM mTLS 도달 (인증 없는 healthz, 단 클라이언트 cert 는 필요)
    curl --cacert ca.crt --cert client.crt --key client.key https://sigil-server:8443/v1/healthz
    # Mac 의 watch 대상 한번 건드리기 (예시)
    touch ~/.claude/settings.json   # 또는 policy.yaml 의 targets 경로
    # VM: host_id 별 JSONL 이 생기는지
    ls -R /var/lib/sigil-server/events

`/v1/meta`·fleet 등 read API 는 bearer 토큰이 설정돼 있어야 200 (없으면 404). reachability 확인은 `/v1/healthz` 로.

## 7. 실무 배포 노트

- 서버 inbound 8443 하나. `bind` 로 포트 변경 가능. 엔드포인트 fleet 는 egress-only(서버:8443 로 나가는 것만 허용) — agent 호스트엔 inbound 룰 불필요.
- Reverse proxy 주의. 서버가 mTLS 클라이언트 cert 를 직접 검증한다. 앞단 L7 proxy 가 TLS 종료하면 client cert 가 안 넘어가 인증 실패 → L4 TLS passthrough 또는 직결로.
- 시간 동기(chrony/NTP). cert 유효기간 · 라이선스 `not_after` · audit/이벤트 timestamp 가 시계에 의존. 양쪽 skew 크면 TLS 만료·서명 검증 오판.
- DNS. 실무는 서버 FQDN A레코드 + server cert SAN = 그 FQDN. `server_base_url` 은 FQDN. (테스트만 `/etc/hosts`.)
- SELinux (RHEL 계열). enforcing 환경에서 비표준 포트 바인드 / `/var/lib/sigil-server` 쓰기에 라벨 필요할 수 있음. `sigil doctor` 가 SELinux·소켓 perms·systemd·events 디렉터리 4-check 로 점검.
- 아키텍처. cross-arch(aarch64/armv7) prebuilt 패키징은 미구현(issue #12). ARM 호스트는 소스 빌드(네이티브) 또는 해당 arch 에서 직접 rpm 빌드.
- per-host 클라이언트 cert. 실무는 호스트마다 client cert 발급(provisioning). CA 로테이션·만료 운영 필요. host_id 는 cert 와 분리된 state.db UUIDv4.
- host 화이트리스트(옵션). server.yaml 의 `host_allowlist_path`(JSON `{"hosts":["uuid",...]}`)로 수용 host_id 제한 가능.
- SIEM 직접 경로. sigil-server 대신 기존 SIEM 으로 바로 보낼 땐 sender 의 `server_base_url` 을 SIEM HTTPS 엔드포인트로 — 그 경우 열 포트는 SIEM 쪽 규격(예 Splunk HEC 8088, 일반 443).

## 8. 흔한 실패 3 + α

1. VM 이 NAT (bridged 아님) → Mac 에서 `curl` timeout. bridged 로 VM 이 LAN IP 받게.
2. server cert SAN 에 접속 주소 없음 → TLS handshake 실패. SAN 에 `DNS:sigil-server`(+/etc/hosts) 또는 `IP:192.168.0.50`.
3. agent 실제 events/control 경로 ≠ sender.yaml → sender 가 spool 못 읽음/apply_policy 못 보냄. 양쪽 경로 명시·일치.
4. 방화벽 8443 미개방 / 서버가 `127.0.0.1` 바인드 → 외부 도달 불가. `0.0.0.0:8443` + firewalld 허용.
5. server.yaml 의 TLS 3필드 중 일부만 설정 → mTLS OFF(PLAIN HTTP 경고). 셋 다 켜야 mTLS.
6. x86_64 rpm 을 aarch64 VM 에 설치 시도 → arch 불일치. 소스 빌드로.

이 문서는 테스트/배포 레퍼런스 (repo 미커밋, 본인 환경값 포함). 차트는 mermaid (CDN 렌더) — 오프라인이면 차트 소스가 텍스트로 보이고, GitHub/IDE preview 에서는 렌더됨. 일반화해서 정식 배포 doc 로 넣고 싶으면 IP/경로를 placeholder 로 바꿔 커밋하면 됩니다.