101 lines
5 KiB
Markdown
101 lines
5 KiB
Markdown
|
|
# Runbook — Relais TURN (visio sur les réseaux qui bloquent l'UDP)
|
||
|
|
|
||
|
|
Voir [ADR 0006](../adr/0006-turn-443-visio.md) 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
|
||
|
|
```
|