Files
web-terminal/deploy/frp/README.md
Yaojia Wang 5337281e85 feat(deploy): device client-CA + issuance/revocation + frp/nginx mTLS templates (V2/V7)
Committable VPS artifacts for the native reverse-tunnel (no live-ops, no secrets):
- gen-device-ca.sh: EC P-256 self-signed device CA (CA:TRUE pathlen:0, keyCertSign+cRLSign),
  openssl ca db + empty CRL, idempotent, --kind frp-client scaffolds the control-channel CA.
- issue-device-cert.sh: P-256 leaf, EKU=clientAuth only, 825d, emits .p12 (-legacy) + .pem/.key/.fullchain,
  copy_extensions=none prevents CSR-injected serverAuth/CA:TRUE; CN traversal guard.
- revoke-device.sh: openssl ca revoke -> regenerate crl.pem (revocation enforced via -crl_check).
- frp/{frps.toml.example,frpc.example.toml}: control-channel mTLS+token, disableCustomTLSFirstByte=true.
- nginx/frp-mtls.conf: :8470 TLS-term, ssl_verify_client on + device-CA + ssl_crl, WS-upgrade proxy to :7080.
- nginx/stream-sni-additions.md: the two additive SNI map lines (merge-only, coexistence-safe).
Verified: gen->issue->openssl verify->revoke->CRL chain green in a tmp dir (OpenSSL 3.0.18).
2026-07-07 09:42:12 +02:00

74 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 V1V5)
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).