Files
prosody/coturn/turnserver.conf
mguschin bf8f50ea72 Stage TLS cert for coturn so turns: works.
coturn drops to nobody, but Let's Encrypt keeps privkey.pem root:root
0600, so it could not read the key and started without TLS listeners --
turns: on 5349 was silently unavailable while STUN/TURN on 3478 worked.

Copy the cert at container start into a directory owned by the runtime
user, the same approach the Prosody entrypoint already uses here, rather
than relaxing permissions on the shared Let's Encrypt tree and exposing
the key to every other container on the host.

The container now starts as root for the copy and coturn drops privileges
itself via proc-user/proc-group. Since the copy happens at start, a
renewal requires a restart, matching the existing Prosody deploy hook.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:21:57 +03:00

79 lines
3.0 KiB
Plaintext

# coturn TURN/STUN server for XMPP Jingle audio/video calls
#
# Credentials are NOT stored here. Prosody's mod_turn_external mints
# time-limited credentials using the shared secret (see static-auth-secret),
# and hands them to clients over XEP-0215. Keep the secret in sync between
# this file and TURN_SECRET in .env.
# Listening sockets. The container runs with network_mode: host, so these
# bind directly to the server's public interface.
listening-port=3478
tls-listening-port=5349
# Pin both the listening and relay addresses to the public interface.
# Without these, coturn auto-discovers every interface and hands out relay
# candidates on the Docker bridges (172.x, 192.168.x) and loopback, which
# remote peers cannot reach and which needlessly expose internal networks.
listening-ip=176.124.216.197
relay-ip=176.124.216.197
# Public address advertised in relay candidates. Without this, coturn hands
# out its own view of the interface address, which breaks behind any NAT.
external-ip=176.124.216.197
# Relay port range. Each concurrent call leg consumes a port from this range.
min-port=49160
max-port=49200
# Authentication: long-term credentials derived from a shared secret
# (REST API / TURN time-limited credentials, as used by XEP-0215).
use-auth-secret
static-auth-secret=e7b94c2d2af838f332a2173a5318a336bdf3cfdb812e5842266d5cd6e6e31d60
realm=guschin.info
# TLS for turns:. Uses the existing multi-SAN guschin.info certificate, which
# is why the advertised TURN host is guschin.info and not turn.guschin.info
# (the latter is not in the cert's SANs, so TLS validation would fail).
# These are the copies staged by entrypoint.sh, not the Let's Encrypt
# originals: the real privkey.pem is root-only and coturn runs as nobody.
cert=/var/lib/coturn/certs/fullchain.pem
pkey=/var/lib/coturn/certs/privkey.pem
# Drop privileges after binding. The container starts as root only so the
# entrypoint can stage the certificate.
proc-user=nobody
proc-group=nogroup
# Harden: this is a relay for our own users, not an open proxy.
# Deny relaying to private ranges so TURN can't be used to reach internal
# services (loopback, RFC1918, link-local, CGNAT, multicast).
no-multicast-peers
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.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
# No TURN-over-plain-old-CLI admin interface, no SQLite/Redis state.
no-cli
# Disable legacy/weak mechanisms.
no-tlsv1
no-tlsv1_1
# Per-user relay caps, to limit the blast radius of a leaked credential.
user-quota=12
total-quota=1200
# Logging to stdout so `docker logs coturn` works.
log-file=stdout
simple-log