feat(turn): self-hosted coturn support + time-limited creds + failure UX

- /api/ice: when TURN_SECRET is set, mint short-lived HMAC credentials
  (coturn use-auth-secret) so no permanent password is exposed and the relay
  can't be abused. Static TURN_USERNAME/CREDENTIAL still supported.
- share.html: connection watchdog + clear "couldn't connect on this network"
  message instead of a blank screen when no path can be established.
- deploy/coturn: ready-to-run turnserver.conf + docker-compose + README for
  hosting our own TURN on a VM we own (flat cost, no per-GB billing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 14:36:05 +05:30
parent 6ac280f178
commit 54b74d5db1
5 changed files with 121 additions and 8 deletions
+44
View File
@@ -0,0 +1,44 @@
# Self-hosted TURN (coturn) for BizGaze Connect
Why: customers behind symmetric NAT / corporate firewalls / VPNs can't form a direct
WebRTC path, so screen share blanks out and disconnects. A TURN relay fixes it. We host
our own coturn on a VM we already own — flat cost, no per-GB billing.
## 1. VM prerequisites
- A VM with a **public IP** (your data-center VM is fine).
- A DNS A record, e.g. `turn.yourdomain.com` -> that public IP.
- A TLS cert for that name (Let's Encrypt): `certbot certonly --standalone -d turn.yourdomain.com`
## 2. Open firewall ports (on the VM and any edge firewall)
- `3478/udp` and `3478/tcp` (STUN/TURN)
- `5349/tcp` (TURN over TLS) — and `443/tcp` if you enable alt-tls
- `49152-65535/udp` (relay range)
## 3. Configure
Edit `turnserver.conf`:
- `external-ip=` your VM's public IP
- `static-auth-secret=` a long random string (e.g. `openssl rand -hex 32`)
- `realm=` your domain
- `cert=` / `pkey=` paths to your Let's Encrypt cert
## 4. Run
```
docker compose up -d # uses docker-compose.yml here
# or native: apt install coturn; copy this file to /etc/turnserver.conf; enable in /etc/default/coturn; systemctl enable --now coturn
```
## 5. Point the app at it (production env)
```
TURN_URLS=turn:turn.yourdomain.com:3478,turn:turn.yourdomain.com:3478?transport=tcp,turns:turn.yourdomain.com:5349?transport=tcp
TURN_SECRET=<the same static-auth-secret from turnserver.conf>
TURN_TTL=86400
```
The app's `/api/ice` mints short-lived credentials from `TURN_SECRET` automatically — no
permanent password is exposed, and outsiders can't reuse your relay. Restart the app.
## 6. Verify
- `GET https://<app>/api/ice` should return a `turn:`/`turns:` entry with a username + credential.
- Test page: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
Add your `turns:turn.yourdomain.com:5349?transport=tcp` with the username/credential from
`/api/ice`; you should see a candidate of type **relay**. If you do, restrictive networks
are covered.
+12
View File
@@ -0,0 +1,12 @@
# Run coturn on your VM: docker compose up -d
# host networking is required so the UDP relay port range works without per-port mapping.
services:
coturn:
image: coturn/coturn:latest
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro # TLS cert for turns:
command: ["-c", "/etc/coturn/turnserver.conf"]
+45
View File
@@ -0,0 +1,45 @@
# coturn config for BizGaze Connect self-hosted TURN.
# Put this on your VM (public IP) and run via Docker (see docker-compose.yml) or
# native coturn (apt install coturn). Replace every CHANGE_ME / placeholder.
# --- listening ---
listening-port=3478
tls-listening-port=5349
# If this VM has a spare 443, also exposing TURNS on 443 gives the best traversal
# through strict corporate firewalls (uncomment + ensure nothing else uses 443):
# alt-tls-listening-port=443
# Public address clients reach. If the VM has a 1:1 NAT, use external-ip=PUBLIC/PRIVATE.
external-ip=CHANGE_ME_PUBLIC_IP
# Relay port range (open these UDP ports in the firewall too).
min-port=49152
max-port=65535
# --- auth: time-limited shared-secret credentials (matches the app's TURN_SECRET) ---
use-auth-secret
static-auth-secret=CHANGE_ME_LONG_RANDOM_SECRET
realm=connect.yourdomain.com
# --- TLS (needed for turns: on 5349/443). Use a real cert for turn.yourdomain.com ---
cert=/etc/letsencrypt/live/turn.yourdomain.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.yourdomain.com/privkey.pem
# --- hardening ---
fingerprint
no-cli
no-multicast-peers
no-tcp-relay
# Block relaying to private/internal ranges (prevents your relay being used to reach
# your own LAN / cloud metadata — important SSRF protection):
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=::1
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
# Optional: cap per-session bandwidth (bytes/sec) to protect the VM, e.g. 700000 = ~5.6 Mbps
# bps-capacity=0
# total-quota=100