Closes FUNCTIONAL_AUDIT.md §4 #1: WebRTC 1:1 calls had working signaling but no NAT traversal, so calls between two peers behind symmetric NAT (corporate firewalls, mobile carrier CGNAT, Incus container default networking) failed silently after the SDP exchange. Backend: - GET /api/v1/config/webrtc (public) returns {iceServers: [...]} built from WEBRTC_STUN_URLS / WEBRTC_TURN_URLS / *_USERNAME / *_CREDENTIAL env vars. Half-config (URLs without creds, or vice versa) deliberately omits the TURN block — a half-configured TURN surfaces auth errors at call time instead of falling back cleanly to STUN-only. - 4 handler tests cover the matrix. Frontend: - services/api/webrtcConfig.ts caches the config for the page lifetime and falls back to the historical hardcoded Google STUN if the fetch fails. - useWebRTC fetches at mount, hands iceServers synchronously to every RTCPeerConnection, exposes a {hasTurn, loaded} hint. - CallButton tooltip warns up-front when TURN isn't configured instead of letting calls time out silently. Ops: - infra/coturn/turnserver.conf — annotated template with the SSRF- safe denied-peer-ip ranges, prometheus exporter, TLS for TURNS, static lt-cred-mech (REST-secret rotation deferred to v1.1). - infra/coturn/README.md — Incus deploy walkthrough, smoke test via turnutils_uclient, capacity rules of thumb. - docs/ENV_VARIABLES.md gains a 13bis. WebRTC ICE servers section. Coturn deployment itself is a separate ops action — this commit lands the plumbing so the deploy can light up the path with zero code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
3.8 KiB
Text
100 lines
3.8 KiB
Text
# coturn configuration template — Veza v1.0.9 item 1.2
|
|
#
|
|
# Deploy onto an Incus container reachable from the public internet on
|
|
# UDP/3478 (TURN) and UDP/49152-65535 (relay range). The container
|
|
# **must** be on the host network or have UDP NAT forwarded for those
|
|
# ranges — TURN traffic does NOT survive Docker/Incus default NAT.
|
|
#
|
|
# Substitute the ${UPPERCASE} tokens via your secrets manager before
|
|
# rendering this file (Ansible `ansible.builtin.template`, sops, age,
|
|
# etc.). Never commit the rendered version to git.
|
|
#
|
|
# Smoke test post-deploy:
|
|
# $ turnutils_uclient -u veza -w "${WEBRTC_TURN_CREDENTIAL}" \
|
|
# -p 3478 turn.veza.fr
|
|
# (succeeds = relay works; failure = check listening-ip / external-ip)
|
|
|
|
# --- Listening setup -------------------------------------------------
|
|
|
|
# Listen on every interface so coturn picks up the public IP regardless
|
|
# of how the container's network stack is wired.
|
|
listening-port=3478
|
|
tls-listening-port=5349
|
|
|
|
# Public reachable IP. REQUIRED behind NAT — without it, coturn
|
|
# advertises the container's RFC1918 address in candidates and clients
|
|
# can't reach the relay.
|
|
external-ip=${WEBRTC_TURN_PUBLIC_IP}
|
|
|
|
# UDP relay port range. 16k ports is the common-sense default; raise if
|
|
# you expect more than ~8k concurrent TURN sessions (each session uses
|
|
# 1-2 ports).
|
|
min-port=49152
|
|
max-port=65535
|
|
|
|
# Realm — used in the TURN handshake. Must match what the frontend
|
|
# sends (it doesn't, today: the frontend sends only urls/username/cred).
|
|
# Set to your domain so logs are unambiguous.
|
|
realm=${WEBRTC_TURN_REALM}
|
|
|
|
# --- Authentication --------------------------------------------------
|
|
|
|
# Static long-term credentials — simple, works, and matches what
|
|
# `internal/handlers/webrtc_config_handler.go` returns to the SPA.
|
|
#
|
|
# v1.0.9 keeps this static. v1.1 should switch to the time-limited REST
|
|
# shared-secret scheme (RFC draft-uberti-behave-turn-rest), which lets
|
|
# the backend mint per-session credentials and rotate without restarting
|
|
# coturn. Documented in ORIGIN_SECURITY_FRAMEWORK.md (deferred section).
|
|
lt-cred-mech
|
|
user=${WEBRTC_TURN_USERNAME}:${WEBRTC_TURN_CREDENTIAL}
|
|
|
|
# --- TLS for TURNS ---------------------------------------------------
|
|
|
|
# Cert chain. Either Let's Encrypt (cert-manager / certbot cron) or a
|
|
# rotated wildcard mounted into the container at these paths.
|
|
cert=/etc/coturn/cert.pem
|
|
pkey=/etc/coturn/key.pem
|
|
|
|
# Modern TLS only. coturn defaults are permissive; tighten here so the
|
|
# whole chain matches the rest of the platform.
|
|
no-tlsv1
|
|
no-tlsv1_1
|
|
|
|
# --- Hardening ------------------------------------------------------
|
|
|
|
# Disable the multiplexed TLS-on-TCP-3478 listener — TURN-over-TCP is
|
|
# slow and we already expose port 5349 for TURNS-over-TLS. Keeps the
|
|
# attack surface predictable.
|
|
no-multicast-peers
|
|
no-cli
|
|
no-loopback-peers
|
|
|
|
# Avoid relaying to RFC1918 / loopback / link-local ranges. Without
|
|
# this, a malicious peer could use Veza's TURN as an SSRF springboard.
|
|
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=127.0.0.0-127.255.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.0.0.0-192.0.0.255
|
|
denied-peer-ip=192.0.2.0-192.0.2.255
|
|
denied-peer-ip=192.88.99.0-192.88.99.255
|
|
denied-peer-ip=192.168.0.0-192.168.255.255
|
|
denied-peer-ip=198.18.0.0-198.19.255.255
|
|
denied-peer-ip=198.51.100.0-198.51.100.255
|
|
denied-peer-ip=203.0.113.0-203.0.113.255
|
|
denied-peer-ip=240.0.0.0-255.255.255.255
|
|
|
|
# --- Observability ---------------------------------------------------
|
|
|
|
# Prometheus exporter on a non-public port. Scrape from the same
|
|
# Prometheus instance that already targets backend-api.
|
|
prometheus
|
|
|
|
# Verbose enough to diagnose, quiet enough not to flood the disk on
|
|
# peak traffic. Bump to `verbose` temporarily during incidents.
|
|
log-file=/var/log/coturn/turnserver.log
|
|
no-stdout-log
|
|
syslog
|