Paillasse/docs/runbooks/turn.md
Adam Belkacemi 45f8a69e26 ajoute le relais TURN sur 443 pour la visio (ADR 0006), avec coturn et le routage Nginx par nom
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-13 11:45:53 +02:00

5 KiB

Runbook — Relais TURN (visio sur les réseaux qui bloquent l'UDP)

Voir ADR 0006 pour le pourquoi complet.

Ce qui tourne

  • coturn, paquet système (pas Docker), service coturn : écoute en TLS (TURNS) sur 127.0.0.1:5349 uniquement. no-udp, no-tcp : aucun autre port, jamais directement sur l'IP publique.
  • Nginx, bloc stream (/etc/nginx/nginx.conf, section balisée « ANSIBLE MANAGED BLOCK — bloc A, ADR 0006 ») : seul process qui écoute le port public 443. Lit le nom demandé dans la poignée de main TLS (SNI), sans la déchiffrer, et transmet les octets tels quels :
    • turn.syllabuslab.fr → 127.0.0.1:5349 (coturn) ;
    • tout le reste → 127.0.0.1:8443 (les vhosts HTTPS du portail, qui n'écoutent plus directement le port public depuis ce chantier).
  • Prosody (rôle visio) : TURNS_HOST/TURNS_PORT/TURN_TTL dans /etc/visio/visio.env, TURN_CREDENTIALS (secret partagé avec coturn) dans /etc/visio/secrets.env. Calcule lui-même, pour chaque participant, un couple nom d'utilisateur/mot de passe TURN valable 24 h — jamais le secret au navigateur.
  • Certificat : turn.syllabuslab.fr fait partie du même certificat Let's Encrypt que le reste (portail_noms_certificat). coturn en a une copie lisible (/etc/turnserver/cert/, groupe turnserver), régénérée automatiquement à chaque renouvellement par /etc/letsencrypt/renewal-hooks/deploy/90-turn-coturn.sh.

Aucun nouveau port ouvert dans nftables. Le relais coturn → JVB (pour la vraie diffusion audio/vidéo) reste en boucle locale (relay-ip=127.0.0.1, JVB en réseau hôte) : il ne traverse jamais l'IP publique.

Rôles et playbooks

Rôle Rôle Ansible Playbook
coturn ansible/roles/turn/ ansible/turn.yml
Routage 443 par nom ansible/roles/portail/tasks/07-turn-stream.yml ansible/portail.yml (et tout playbook qui rejoue portail)
Annonce côté Jitsi ansible/roles/visio/ (tâches TURN) ansible/visio.yml

Ordre de déploiement important : turn.yml doit tourner avant visio.yml (Prosody lit le secret de coturn, pas l'inverse). portail.yml doit avoir déjà étendu le certificat au nom turn avant que turn.yml tente de copier /etc/letsencrypt/live/.../.

Rotation du secret partagé

Le secret est dans /etc/turnserver-secret (600 root:root). Ne jamais l'afficher (ni cat, ni dans une commande dont la sortie serait relue).

  1. Sur le serveur : sudo bash -c 'openssl rand -hex 32 > /etc/turnserver-secret; chmod 600 /etc/turnserver-secret'
  2. Depuis le Mac : ansible-playbook turn.yml -i inventory.ini (redéploie coturn avec le nouveau secret, le redémarre).
  3. Puis : ansible-playbook visio.yml -i inventory.ini (Prosody relit le secret, les conteneurs Jitsi redémarrent).
  4. Vérifier que les deux sont synchronisés sans afficher le secret : comparer les empreintes (sha256sum) du secret réellement chargé par chacun, jamais leur contenu. Exemple (coturn) :
    sudo grep '^static-auth-secret=' /etc/turnserver.conf | sed 's/^static-auth-secret=//' | tr -d '\n' | sha256sum
    
    et côté Prosody (dans le conteneur) :
    sudo docker exec visio-prosody-1 grep external_service_secret /run/prosody/config/prosody.cfg.lua | sed -E 's/.*"(.*)".*/\1/' | tr -d '\n' | sha256sum
    
    Les deux empreintes doivent être identiques.

Ne jamais lancer deux ansible-playbook en même temps sur ce serveur : un incident le 2026-10-13 a montré qu'ils partagent la connexion SSH (control socket) — tuer l'un peut faire planter un worker de l'autre en plein milieu, laissant une configuration à moitié appliquée (sans la casser : revérifier l'état avant de relancer).

Vérifier que le relais fonctionne (sans navigateur)

SECRET=$(sudo cat /etc/turnserver-secret)   # sur le serveur seulement
timeout 10 turnutils_uclient -t -S -p 443 -W "$SECRET" -e 127.0.0.1 -r 10000 -n 1 turn.syllabuslab.fr
unset SECRET

Une ligne tls_connect: client session connected suivie d'une réponse binaire (pas une erreur HTTP) prouve que le routage par nom atteint bien coturn. (coturn ne journalise pas ces échanges par défaut — pas de ligne dans journalctl -u coturn à attendre.)

Vérifier de bout en bout avec un vrai participant (à faire avant le pilote)

Forcer un navigateur à ignorer l'UDP (ex. Chrome : chrome://webrtc-internals ou bloquer l'UDP sortant sur un réseau de test) et vérifier dans les statistiques WebRTC qu'une candidate ICE de type relay est utilisée. Rejoue aussi portail/tests/Reels/visio-e2e.mjs.

Tout vérifier après un changement de certificat ou de Nginx

sudo nginx -t
curl -s -o /dev/null -w '%{http_code}\n' https://syllabuslab.fr
echo | openssl s_client -connect syllabuslab.fr:443 -servername turn.syllabuslab.fr 2>/dev/null | openssl x509 -noout -subject -enddate
nmap -Pn -T4 --open 163.172.4.195   # doit toujours montrer seulement 22, 80, 443