# Native mTLS Tunnel — per-host runbook (V7) Bring one local host onto the tunnel so native clients (iOS / Android / desktop) can reach its base app at `https://.terminal.yaojia.wang` over the VPS, gated **only** by device-certificate mTLS. See `docs/PLAN_NATIVE_TUNNEL.md` for the full design; this is the short operational checklist. **Trust model: A — single-owner fleet** (all devices/hosts are the operator's). A shared device-CA means "the closed set of my devices"; there is **no cross-tenant isolation** — do not enroll distrusting third parties on this CA (that is Model B, out of scope for v1). ## Files here | File | Install target on the VPS | Purpose | |---|---|---| | `frps.toml.example` | `/etc/relay/frp/frps.toml` (0600) | frps control `:7000` + vhost `:7080` | | `frpc.example.toml` | on each LOCAL host (0600) | dials the VPS, exposes `127.0.0.1:3000` | | `../nginx/frp-mtls.conf` | `/etc/nginx/conf.d/frp-mtls.conf` | `:8470` TLS-term + device-CA mTLS + WS proxy | | `../nginx/stream-sni-additions.md` | (merge into the live stream map) | the two additive SNI routes | ## One-time VPS setup (summarised — see PLAN V1–V5) 1. **frps** (`frps.toml.example` → `/etc/relay/frp/frps.toml`): set `auth.token` = `openssl rand -hex 32`. 2. **frp-client CA** (control-channel mTLS): `bash deploy/scripts/gen-device-ca.sh --kind frp-client`. 3. **device CA** (data-path mTLS): `bash deploy/scripts/gen-device-ca.sh` (KIND=device default). 4. **LE wildcard** `*.terminal.yaojia.wang` (V3) → `/etc/relay/frp-tls/{fullchain,privkey}.pem`. 5. **nginx** `:8470` (`frp-mtls.conf`) + the two stream SNI lines (`stream-sni-additions.md`), then `nginx -t && systemctl reload nginx`. ## Onboard a host (the repeatable part) 1. **frp-client cert** for this host (control mTLS), issued from the frp-client CA: ```bash CA_DIR=/etc/relay/frp-client-ca bash deploy/scripts/issue-device-cert.sh ./out- # use out-/.cert.pem + .key.pem for frpc.certFile/keyFile (ignore the .p12) ``` 2. **frpc.toml** on the host from `frpc.example.toml`: set `subdomain=`, the shared `auth.token`, and the frp-client cert/key paths; `chmod 600 frpc.toml`. 3. **Base app** on the host — the tunnel is safe ONLY with these: | Var | Value | Why | |---|---|---| | `BIND_HOST` | `127.0.0.1` | **Mandatory.** Default `0.0.0.0` exposes an unauth'd shell on the LAN, bypassing mTLS. | | `PORT` | `3000` | frpc dials `127.0.0.1:3000`. | | `ALLOWED_ORIGINS` | `https://.terminal.yaojia.wang` | bare host; `:443` also matches (normalized). | | `IDLE_TTL` | `≥ 86400` | walk-away survival. | | `USE_TMUX` | `1` (*nix) / `0` (Windows) | cross-restart PTY survival. | 4. **Device cert** for whoever will connect (data-path mTLS), issued from the device CA: ```bash P12_PASSWORD='...' bash deploy/scripts/issue-device-cert.sh ./out-dev # deliver out-dev/<...>.p12 SECURELY: scp / AirDrop / Files — NEVER email. ``` 5. **Verify** (M1): no-cert → TLS handshake failure; `curl --cert …/.cert.pem --key …/.key.pem -sI https://.terminal.yaojia.wang/live-sessions` → `200`. ## Revoke a device ```bash CA_DIR=/etc/relay/device-ca bash deploy/scripts/revoke-device.sh /path/to/.cert.pem # copy the regenerated /etc/relay/device-ca/crl.pem to the VPS, then ON THE VPS: nginx -t && nginx -s reload ``` The revoked cert is rejected fleet-wide the moment nginx reloads the CRL. ## Trust-boundary note (Model A) - The **frp-client cert + token is a trusted secret**: leaking a host's frp-client cert **and** the shared token lets an attacker register a subdomain (subdomain-takeover / MITM). Keep both `chmod 600`; deliver over scp, never email. Revoke a compromised host by rotating the `frp-client-ca` trust (re-issue the remaining hosts) — device certs are unaffected. - The **device cert** is the data-path gate. Under Model A any valid device cert can reach any subdomain and can read `/live-sessions/:id/preview` scrollback — acceptable because every device is yours. Do not extend this CA to third parties without moving to Model B (per-tenant CA). - **P-256, never Ed25519** for both CAs (nginx `ssl_verify_client` + iOS client-cert compatibility).