# Stealth TURN Deployment Guide ## What this is A TLS-level SNI router that lets Orama serve TURN-over-TLS on `:443`, sharing the port with Caddy HTTPS. From a network observer's perspective, TURN traffic is indistinguishable from ordinary HTTPS — useful for users in regions that block standard VoIP ports (UAE, Saudi Arabia, China, Iran). ## Architecture ``` Internet │ ▼ TCP :443 │ ┌─────────┴─────────┐ │ orama-sni-router │ peeks SNI, forwards bytes └─────────┬─────────┘ │ ┌───────────────┼────────────────┐ ▼ ▼ cdn. *., turn. (everything else) │ │ ▼ ▼ Pion TURN-TLS Caddy 127.0.0.1:5349 127.0.0.1:8443 (existing) (moved from :443) ``` The router does **not** terminate TLS. It reads the unencrypted TLS ClientHello (first ~5 KB), inspects the SNI extension, and dials the matching backend. Encrypted bytes pass through verbatim. ## Components - **Library:** `pkg/sniproxy/` — ClientHello parser, route table, TCP server - **Binary:** `cmd/sni-router/` (built as `bin/orama-sni-router`) - **Systemd unit:** `systemd/orama-sni-router.service` - **Config:** `~/.orama/sni-router.yaml` ## Deployment cutover ⚠️ **This change touches production `:443`. Stage on one node first, watch for 24h, then roll out.** ### 1. Reconfigure Caddy to listen on `:8443` Update wherever the Caddy config is generated (`pkg/environments/production/installers/caddy.go`) so Caddy binds `:8443` (HTTPS) and `:8080` (HTTP) instead of `:443` and `:80`. Drop `CAP_NET_BIND_SERVICE` from Caddy's systemd unit — it no longer needs privileged ports. ### 2. Provision the cert SAN for `cdn.` Caddy's automatic Let's Encrypt flow needs to issue a cert covering `cdn.` and `cdn.ns-*.` so Pion TURN can read it on startup. Add these names to Caddy's TLS config block. ### 3. Drop `sni-router.yaml` config Example for a single-namespace node: ```yaml listen: ":443" client_hello_timeout: 5s backend_dial_timeout: 5s max_concurrent_conns: 10000 fallback: name: caddy addr: "127.0.0.1:8443" routes: - match: "cdn.example.com" backend: name: turn-tls addr: "127.0.0.1:5349" - match: "turn.example.com" backend: name: turn-tls addr: "127.0.0.1:5349" ``` For multi-namespace, add per-namespace TURN backends (each namespace's TURN-TLS port is allocated by `pkg/namespace`): ```yaml - match: "cdn.ns-myapp.example.com" backend: { name: "turn-myapp", addr: "127.0.0.1:5349" } - match: "cdn.ns-other.example.com" backend: { name: "turn-other", addr: "127.0.0.1:5350" } ``` ### 4. Deploy + start in order ```bash # Install binary sudo cp bin-linux/orama-sni-router /opt/orama/bin/ # Install service sudo cp systemd/orama-sni-router.service /etc/systemd/system/ sudo systemctl daemon-reload # Stop Caddy briefly (it's about to lose :443) sudo systemctl stop caddy # Start the SNI router (it takes :443) sudo systemctl enable --now orama-sni-router # Restart Caddy on its new port sudo systemctl start caddy # Verify curl -v https://cdn.:443 # should hit TURN backend (TLS handshake will fail; that's fine) curl -v https://:443 # should hit Caddy (normal HTTPS response) ``` ### 5. Enable stealth in the gateway Once the SNI router is live, tell the gateway to advertise the stealth URI: ```go // in gateway dependencies / startup webrtcHandlers.SetStealthCDNDomain("cdn.") ``` The credentials handler will start including `turns:cdn.:443` in `POST /v1/webrtc/turn/credentials` responses automatically. ### 6. Monitor ```bash journalctl -u orama-sni-router.service -f journalctl -u caddy.service -f ``` Watch for: - `Connection limit reached` warnings (bump `max_concurrent_conns`) - `backend dial failed` warnings (Caddy isn't listening on `:8443`, or TURN isn't on `:5349`) - `ClientHello peek failed` debugs (curious clients sending non-TLS to `:443` — usually port scanners) ## Rollback If anything is wrong: ```bash sudo systemctl stop orama-sni-router # Reconfigure Caddy back to :443 and restart sudo systemctl restart caddy ``` Caddy reclaiming `:443` from the disabled router is the fastest way back to the previous topology. ## Known gaps - **Dynamic route source:** today's router reads YAML once at startup. To pick up new namespaces without restart, implement a `RouteSource` that polls `pkg/namespace` for active TURN deployments. The library is already designed for `Router.Replace` to be called concurrently. - **TLS cert hot-reload:** Pion TURN reads the cert once at startup. When Caddy renews `cdn.`, Pion needs to be restarted to pick up the new cert. A small file-watcher service (or a periodic restart in off-peak hours) handles this for now. ## What clients see Once enabled, the credentials response gains one entry: ```json { "username": "...", "password": "...", "ttl": 600, "uris": [ "turn:turn.example.com:3478?transport=udp", "turn:turn.example.com:3478?transport=tcp", "turns:turn.example.com:5349", "turns:cdn.example.com:443" ] } ``` Browsers iterate ICE candidates; users in restricted regions will silently succeed via the `:443` URI when others fail. No client-side change is required.