# 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://<name>.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 <name> ./out-<name>
   # use out-<name>/<name>.cert.pem + <name>.key.pem for frpc.certFile/keyFile (ignore the .p12)
   ```
2. **frpc.toml** on the host from `frpc.example.toml`: set `subdomain=<name>`, 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://<name>.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 <person-or-device> ./out-dev
   # deliver out-dev/<...>.p12 SECURELY: scp / AirDrop / Files — NEVER email.
   ```
5. **Verify** (M1): no-cert → TLS handshake failure; `curl --cert …/<dev>.cert.pem --key …/<dev>.key.pem
   -sI https://<name>.terminal.yaojia.wang/live-sessions` → `200`.

## Revoke a device

```bash
CA_DIR=/etc/relay/device-ca bash deploy/scripts/revoke-device.sh /path/to/<dev>.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).
