jitsi-meet/infra/k3s/80-coturn.yaml
2026-05-13 14:05:43 +02:00

165 lines
5.6 KiB
YAML

# coturn — STUN+TURN relay for clients whose network can't reach JVB
# directly. Hosted on the same node as JVB; hostNetwork: true so the
# TURN listening ports bind on the public NIC. The TLS cert is the
# same one cert-manager already issues for the jitsi-web Ingress —
# we mount the jitsi-tls Secret as a volume. Watch out: when the cert
# renews, this pod must be restarted to pick up the new file (the
# weekly CronJob below handles that).
#
# Auth model: HMAC time-limited credentials. coturn validates with
# `use-auth-secret` + `static-auth-secret=<TURN_CREDENTIALS_SECRET>`;
# Prosody hands out matching credentials per session via
# mod_external_services. Both read the secret from the same k8s
# Secret entry so they stay in lockstep.
#
# Port plan (mirrors required FritzBox forwards):
# UDP 3479 STUN + TURN (cleartext) — 3478 is taken by an
# OpenDesk-bundled Nextcloud-Talk TURN on this host,
# same shift trick we used on JVB (10000 → 10001).
# TCP 3479 TURN over TCP — first cleartext fallback
# UDP 5349 TURN over DTLS
# TCP 5349 TURN over TLS — works through most firewalls
# UDP 50000-50100 relay range — actual media flows on these
apiVersion: apps/v1
kind: Deployment
metadata:
name: coturn
namespace: jitsi
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: { app: coturn }
template:
metadata:
labels: { app: coturn }
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: coturn
image: coturn/coturn:4.7-alpine
# Inline-template the config so we can interpolate the env-var
# secret without an extra ConfigMap-then-envsubst dance.
command: ["/bin/sh", "-c"]
args:
- |
set -eu
cat > /tmp/turnserver.conf <<EOF
listening-port=3479
tls-listening-port=5349
listening-ip=0.0.0.0
external-ip=${PUBLIC_IP}
relay-ip=${PUBLIC_IP}
cert=/certs/tls.crt
pkey=/certs/tls.key
# Jitsi/Prosody hands out HMAC-signed time-limited creds
# using this same secret — coturn validates them.
use-auth-secret
static-auth-secret=${TURN_CREDENTIALS_SECRET}
realm=meet.it.financeflow.de
# Relay port range. Forward these on the router.
min-port=50000
max-port=50100
log-file=stdout
simple-log
no-loopback-peers
no-multicast-peers
no-tlsv1
no-tlsv1_1
fingerprint
# Don't allow relaying to internal addresses — TURN should
# only relay to the public internet.
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
EOF
exec turnserver -c /tmp/turnserver.conf
env:
- name: PUBLIC_IP
value: "__PUBLIC_IP__"
- name: TURN_CREDENTIALS_SECRET
valueFrom:
secretKeyRef:
name: jitsi-secrets
key: TURN_CREDENTIALS_SECRET
ports:
- { name: stun-udp, containerPort: 3479, hostPort: 3479, protocol: UDP }
- { name: stun-tcp, containerPort: 3479, hostPort: 3479, protocol: TCP }
- { name: turns-udp, containerPort: 5349, hostPort: 5349, protocol: UDP }
- { name: turns-tcp, containerPort: 5349, hostPort: 5349, protocol: TCP }
volumeMounts:
- { name: tls, mountPath: /certs, readOnly: true }
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 1, memory: 512Mi }
volumes:
- name: tls
secret:
secretName: jitsi-tls
items:
- { key: tls.crt, path: tls.crt }
- { key: tls.key, path: tls.key }
---
# Cert-manager renews the Let's-Encrypt cert ~30 days before expiry.
# The Secret content updates automatically, but coturn keeps the old
# in-memory cert until it's restarted. Restart once a week — Sundays
# 03:00 — gives us plenty of slack vs. the 90-day cert lifetime and
# 30-day renewal window.
apiVersion: batch/v1
kind: CronJob
metadata:
name: coturn-cert-refresh
namespace: jitsi
spec:
schedule: "0 3 * * 0"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 2
jobTemplate:
spec:
template:
spec:
serviceAccountName: coturn-restarter
restartPolicy: OnFailure
containers:
- name: kubectl
image: bitnami/kubectl:1.30
command: ["/bin/sh", "-c"]
args:
- kubectl -n jitsi rollout restart deployment/coturn
---
# Minimal RBAC for the CronJob — only allows rolling-restart of the
# coturn Deployment, nothing else. Scoped to the jitsi namespace.
apiVersion: v1
kind: ServiceAccount
metadata:
name: coturn-restarter
namespace: jitsi
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: coturn-restarter
namespace: jitsi
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
resourceNames: ["coturn"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: coturn-restarter
namespace: jitsi
subjects:
- kind: ServiceAccount
name: coturn-restarter
namespace: jitsi
roleRef:
kind: Role
name: coturn-restarter
apiGroup: rbac.authorization.k8s.io