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>
This commit is contained in:
parent
ee079ddffb
commit
45f8a69e26
19 changed files with 457 additions and 14 deletions
|
|
@ -19,3 +19,8 @@ portail_port_uptime_kuma: 3001
|
|||
# Étape 15 : visio (Jitsi, rôle visio), activée quand ce nom résout vers le serveur.
|
||||
portail_domaine_visio: meet.syllabuslab.fr
|
||||
portail_port_visio: 8090
|
||||
# Bloc A (ADR 0006) : relais TURN sur 443 pour la visio (réseaux scolaires qui
|
||||
# bloquent l'UDP). Dans le certificat seulement — jamais un vhost HTTPS : le
|
||||
# routeur par nom (stream Nginx) l'envoie directement à coturn.
|
||||
portail_domaine_turn: turn.syllabuslab.fr
|
||||
portail_port_turn_tls: 5349
|
||||
|
|
|
|||
|
|
@ -32,14 +32,22 @@
|
|||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: noter si la page de statut et la visio peuvent être activées
|
||||
- name: vérifier le DNS du sous-domaine du relais TURN
|
||||
ansible.builtin.command: getent ahostsv4 {{ portail_domaine_turn }}
|
||||
register: portail_dns_turn
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: noter si la page de statut, la visio et le relais TURN peuvent être activés
|
||||
ansible.builtin.set_fact:
|
||||
portail_statut_actif: "{{ portail_ip_publique in portail_dns_statut.stdout }}"
|
||||
portail_visio_actif: "{{ portail_ip_publique in portail_dns_visio.stdout }}"
|
||||
portail_turn_actif: "{{ portail_ip_publique in portail_dns_turn.stdout }}"
|
||||
portail_noms_certificat: >-
|
||||
{{ [portail_domaine] + portail_domaines_alias
|
||||
+ ([portail_domaine_statut] if portail_ip_publique in portail_dns_statut.stdout else [])
|
||||
+ ([portail_domaine_visio] if portail_ip_publique in portail_dns_visio.stdout else []) }}
|
||||
+ ([portail_domaine_visio] if portail_ip_publique in portail_dns_visio.stdout else [])
|
||||
+ ([portail_domaine_turn] if portail_ip_publique in portail_dns_turn.stdout else []) }}
|
||||
|
||||
- name: vérifier si le certificat existe déjà
|
||||
ansible.builtin.stat:
|
||||
|
|
|
|||
37
ansible/roles/portail/tasks/07-turn-stream.yml
Normal file
37
ansible/roles/portail/tasks/07-turn-stream.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
# Bloc A (ADR 0006) : partage du port 443 entre le portail et le relais TURN
|
||||
# (coturn, rôle turn), par nom de domaine (SNI), sans jamais déchiffrer le
|
||||
# TLS existant. N'ouvre aucun nouveau port dans nftables : toujours 443.
|
||||
- name: installer le module stream de Nginx
|
||||
ansible.builtin.apt:
|
||||
name: libnginx-mod-stream
|
||||
state: present
|
||||
when: portail_turn_actif | default(false)
|
||||
notify: recharger nginx
|
||||
|
||||
- name: router le port 443 public par nom (stream Nginx)
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/nginx/nginx.conf
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK — bloc A, ADR 0006, relais TURN"
|
||||
insertafter: EOF
|
||||
block: |
|
||||
# Le vrai TLS (nos vhosts HTTPS, désormais sur 127.0.0.1:8443) et
|
||||
# coturn (127.0.0.1:{{ portail_port_turn_tls }}) se partagent le port public
|
||||
# 443 : ce bloc regarde seulement le nom demandé dans la poignée de
|
||||
# main TLS (SNI), jamais son contenu déchiffré, puis transmet les
|
||||
# octets tels quels (aucun des deux ne voit l'autre).
|
||||
stream {
|
||||
map $ssl_preread_server_name $syllabus_stream_backend {
|
||||
{{ portail_domaine_turn }} 127.0.0.1:{{ portail_port_turn_tls }};
|
||||
default 127.0.0.1:8443;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
listen [::]:443;
|
||||
ssl_preread on;
|
||||
proxy_pass $syllabus_stream_backend;
|
||||
}
|
||||
}
|
||||
when: portail_turn_actif | default(false)
|
||||
notify: recharger nginx
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
- name: Nginx
|
||||
ansible.builtin.import_tasks: 05-nginx.yml
|
||||
|
||||
- name: relais TURN sur 443 (bloc A, ADR 0006)
|
||||
ansible.builtin.import_tasks: 07-turn-stream.yml
|
||||
|
||||
- name: file et planificateur (jobs Heat, expiration des sessions)
|
||||
ansible.builtin.import_tasks: 06-queue-scheduler.yml
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
# Géré par Ansible (rôle portail) — ne pas modifier à la main.
|
||||
{# Bloc A (ADR 0006) : quand le relais TURN est actif, le port public 443
|
||||
est pris par le routeur Nginx par nom (stream, nginx.conf) — ces vhost
|
||||
HTTPS n'écoutent alors plus qu'en interne, sur 127.0.0.1:8443, où ce
|
||||
routeur leur transmet tout ce qui n'est pas pour coturn. #}
|
||||
{%- macro listen443(default_server=False) -%}
|
||||
{%- if portail_turn_actif | default(false) %}
|
||||
listen 127.0.0.1:8443 ssl http2{{ ' default_server' if default_server }};
|
||||
{%- else %}
|
||||
listen 443 ssl http2{{ ' default_server' if default_server }};
|
||||
listen [::]:443 ssl http2{{ ' default_server' if default_server }};
|
||||
{%- endif -%}
|
||||
{% endmacro -%}
|
||||
{% macro bloc_portail() %}
|
||||
root {{ portail_dossier_app }}/public;
|
||||
index index.php;
|
||||
|
|
@ -40,7 +52,7 @@
|
|||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ portail_domaine }} {{ portail_domaines_alias | join(' ') }}{% if portail_statut_actif %} {{ portail_domaine_statut }}{% endif %}{% if portail_visio_actif | default(false) %} {{ portail_domaine_visio }}{% endif %};
|
||||
server_name {{ portail_domaine }} {{ portail_domaines_alias | join(' ') }}{% if portail_statut_actif %} {{ portail_domaine_statut }}{% endif %}{% if portail_visio_actif | default(false) %} {{ portail_domaine_visio }}{% endif %}{% if portail_turn_actif | default(false) %} {{ portail_domaine_turn }}{% endif %};
|
||||
server_tokens off;
|
||||
# Dans une location (pas au niveau server) : la location exacte que
|
||||
# certbot ajoute pour le défi ACME au renouvellement reste prioritaire.
|
||||
|
|
@ -61,8 +73,7 @@ server {
|
|||
|
||||
# www → domaine principal (une seule adresse canonique).
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
{{ listen443() }}
|
||||
server_name {{ portail_domaines_alias | join(' ') }};
|
||||
server_tokens off;
|
||||
include snippets/syllabus-tls.conf;
|
||||
|
|
@ -71,8 +82,7 @@ server {
|
|||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
{{ listen443(default_server=True) }}
|
||||
server_name {{ portail_domaine }};
|
||||
server_tokens off;
|
||||
include snippets/syllabus-tls.conf;
|
||||
|
|
@ -85,8 +95,7 @@ server {
|
|||
# (tableau de bord, /socket.io, /metrics…) reste accessible par tunnel SSH
|
||||
# uniquement. Chemins relevés dans server/routers/status-page-router.js.
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
{{ listen443() }}
|
||||
server_name {{ portail_domaine_statut }};
|
||||
server_tokens off;
|
||||
include snippets/syllabus-tls.conf;
|
||||
|
|
@ -123,8 +132,7 @@ server {
|
|||
# X-Frame-Options et sa Permissions-Policy interdiraient l'intégration dans
|
||||
# le portail et la caméra. Intégration permise au seul portail.
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
{{ listen443() }}
|
||||
server_name {{ portail_domaine_visio }};
|
||||
server_tokens off;
|
||||
include snippets/syllabus-tls.conf;
|
||||
|
|
|
|||
10
ansible/roles/turn/defaults/main.yml
Normal file
10
ansible/roles/turn/defaults/main.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Bloc A (ADR 0006) : relais TURN sur 443 pour la visio, réservé aux réseaux
|
||||
# scolaires qui bloquent l'UDP. coturn en paquet système (pas Docker : un
|
||||
# seul process à durcir, pas de souci de réseau Docker comme avec Jitsi/Kuma).
|
||||
turn_secret_fichier: /etc/turnserver-secret
|
||||
turn_port_tls: 5349
|
||||
turn_realm: syllabuslab.fr
|
||||
# Nom du certificat Let's Encrypt (rôle portail, portail_domaine) : dupliqué
|
||||
# ici plutôt que de dépendre de ce rôle, pour que `turn` reste autonome.
|
||||
turn_domaine_certificat: syllabuslab.fr
|
||||
5
ansible/roles/turn/handlers/main.yml
Normal file
5
ansible/roles/turn/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: redémarrer coturn
|
||||
ansible.builtin.systemd:
|
||||
name: coturn
|
||||
state: restarted
|
||||
79
ansible/roles/turn/tasks/main.yml
Normal file
79
ansible/roles/turn/tasks/main.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
# Bloc A (ADR 0006) : relais TURN sur 443 pour la visio. coturn n'écoute
|
||||
# qu'en TLS, sur 127.0.0.1 : jamais directement sur l'IP publique — c'est le
|
||||
# routeur Nginx par nom (rôle portail) qui lui amène le trafic du port 443.
|
||||
- name: installer coturn
|
||||
ansible.builtin.apt:
|
||||
name: coturn
|
||||
state: present
|
||||
|
||||
- name: vérifier si le secret existe déjà
|
||||
ansible.builtin.stat:
|
||||
path: "{{ turn_secret_fichier }}"
|
||||
register: turn_secret_stat
|
||||
|
||||
- name: générer le secret partagé une seule fois
|
||||
ansible.builtin.command: openssl rand -hex 32
|
||||
register: turn_secret_genere
|
||||
changed_when: false
|
||||
when: not turn_secret_stat.stat.exists
|
||||
|
||||
- name: déposer le secret (une seule fois)
|
||||
ansible.builtin.copy:
|
||||
content: "{{ turn_secret_genere.stdout }}\n"
|
||||
dest: "{{ turn_secret_fichier }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
when: not turn_secret_stat.stat.exists
|
||||
|
||||
- name: lire le secret
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ turn_secret_fichier }}"
|
||||
register: turn_secret_lu
|
||||
no_log: true
|
||||
|
||||
- name: créer le dossier de la copie lisible du certificat
|
||||
ansible.builtin.file:
|
||||
path: /etc/turnserver/cert
|
||||
state: directory
|
||||
owner: root
|
||||
group: turnserver
|
||||
mode: "0750"
|
||||
|
||||
- name: déployer le script de copie du certificat (Ansible + renouvellement certbot)
|
||||
ansible.builtin.template:
|
||||
src: turn-copier-certificat.sh.j2
|
||||
dest: /usr/local/sbin/syllabus-turn-copier-certificat
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: brancher ce script sur le renouvellement automatique du certificat
|
||||
ansible.builtin.file:
|
||||
src: /usr/local/sbin/syllabus-turn-copier-certificat
|
||||
dest: /etc/letsencrypt/renewal-hooks/deploy/90-turn-coturn.sh
|
||||
state: link
|
||||
|
||||
- name: déployer la configuration de coturn
|
||||
ansible.builtin.template:
|
||||
src: turnserver.conf.j2
|
||||
dest: /etc/turnserver.conf
|
||||
owner: root
|
||||
group: turnserver
|
||||
mode: "0640"
|
||||
vars:
|
||||
turn_secret: "{{ turn_secret_lu.content | b64decode | trim }}"
|
||||
notify: redémarrer coturn
|
||||
|
||||
- name: activer coturn
|
||||
ansible.builtin.systemd:
|
||||
name: coturn
|
||||
enabled: true
|
||||
|
||||
- name: appliquer la configuration et la copie du certificat
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: copier le certificat pour coturn et le démarrer avec la vraie configuration
|
||||
ansible.builtin.command: /usr/local/sbin/syllabus-turn-copier-certificat
|
||||
changed_when: true
|
||||
10
ansible/roles/turn/templates/turn-copier-certificat.sh.j2
Normal file
10
ansible/roles/turn/templates/turn-copier-certificat.sh.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
# Géré par Ansible (rôle turn) — ne pas modifier à la main.
|
||||
# Copie le certificat Let's Encrypt dans un endroit lisible par coturn (sa
|
||||
# clé privée originale reste 600 root:root, hors de portée de turnserver) et
|
||||
# redémarre coturn. Lancé par Ansible (premier déploiement) et par certbot à
|
||||
# chaque renouvellement (/etc/letsencrypt/renewal-hooks/deploy/).
|
||||
set -euo pipefail
|
||||
install -o root -g turnserver -m 0640 /etc/letsencrypt/live/{{ turn_domaine_certificat }}/fullchain.pem /etc/turnserver/cert/fullchain.pem
|
||||
install -o root -g turnserver -m 0640 /etc/letsencrypt/live/{{ turn_domaine_certificat }}/privkey.pem /etc/turnserver/cert/privkey.pem
|
||||
systemctl restart coturn
|
||||
29
ansible/roles/turn/templates/turnserver.conf.j2
Normal file
29
ansible/roles/turn/templates/turnserver.conf.j2
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Géré par Ansible (rôle turn) — ne pas modifier à la main.
|
||||
# TURNS seulement (TLS), sur 127.0.0.1 : jamais exposé directement à
|
||||
# Internet. Le port public 443 y arrive par le routeur Nginx (SNI, rôle
|
||||
# portail), qui transmet les octets tels quels — coturn gère son propre TLS.
|
||||
listening-ip=127.0.0.1
|
||||
tls-listening-port={{ turn_port_tls }}
|
||||
no-udp
|
||||
no-tcp
|
||||
no-tls-v1
|
||||
no-tls-v1_1
|
||||
|
||||
# Relais vers JVB (rôle visio, réseau de l'hôte) : jamais vers l'extérieur,
|
||||
# toujours en boucle locale.
|
||||
relay-ip=127.0.0.1
|
||||
|
||||
cert=/etc/turnserver/cert/fullchain.pem
|
||||
pkey=/etc/turnserver/cert/privkey.pem
|
||||
|
||||
# Authentification par secret partagé (TURN REST API), le même que
|
||||
# TURN_CREDENTIALS côté Prosody (rôle visio) : Prosody calcule un couple
|
||||
# nom d'utilisateur/mot de passe valable 24 h pour chaque participant, sans
|
||||
# jamais donner ce secret au navigateur.
|
||||
use-auth-secret
|
||||
static-auth-secret={{ turn_secret }}
|
||||
realm={{ turn_realm }}
|
||||
|
||||
fingerprint
|
||||
no-cli
|
||||
no-multicast-peers
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
visio_version: stable-11248
|
||||
visio_domaine: meet.syllabuslab.fr
|
||||
visio_dossier: /opt/visio
|
||||
# Bloc A (ADR 0006) : relais TURN sur 443, activé seulement si le rôle `turn`
|
||||
# est déjà passé sur ce serveur (son secret existe). Nom dupliqué depuis
|
||||
# `portail_domaine_turn`/`turn_domaine_certificat`, pour que `visio` reste
|
||||
# autonome (même principe que pour `turn_domaine_certificat`).
|
||||
visio_domaine_turn: turn.syllabuslab.fr
|
||||
visio_turn_secret_fichier: /etc/turnserver-secret
|
||||
visio_port_web: 8090
|
||||
visio_ip_publique: 163.172.4.195
|
||||
visio_port_media: 10000
|
||||
|
|
|
|||
|
|
@ -45,6 +45,33 @@
|
|||
JWT_APP_SECRET={{ lookup('community.general.random_string', length=64, special=false) }}
|
||||
no_log: true
|
||||
|
||||
# Bloc A (ADR 0006) : le relais TURN est un rôle à part (coturn, pas
|
||||
# Docker) ; ici on se contente de lire s'il existe déjà et de transmettre
|
||||
# son secret à Prosody — jamais l'inverse (le rôle turn ne connaît pas la visio).
|
||||
- name: vérifier si le relais TURN est déjà configuré sur ce serveur
|
||||
ansible.builtin.stat:
|
||||
path: "{{ visio_turn_secret_fichier }}"
|
||||
register: visio_turn_secret_stat
|
||||
|
||||
- name: noter si le relais TURN peut être annoncé aux participants
|
||||
ansible.builtin.set_fact:
|
||||
visio_turn_actif: "{{ visio_turn_secret_stat.stat.exists }}"
|
||||
|
||||
- name: lire le secret du relais TURN
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ visio_turn_secret_fichier }}"
|
||||
register: visio_turn_secret_lu
|
||||
when: visio_turn_actif
|
||||
no_log: true
|
||||
|
||||
- name: transmettre le secret du relais TURN à Prosody
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/visio/secrets.env
|
||||
regexp: "^TURN_CREDENTIALS="
|
||||
line: "TURN_CREDENTIALS={{ visio_turn_secret_lu.content | b64decode | trim }}"
|
||||
when: visio_turn_actif
|
||||
no_log: true
|
||||
|
||||
- name: déployer la configuration de la visio
|
||||
ansible.builtin.template:
|
||||
src: visio.env.j2
|
||||
|
|
|
|||
|
|
@ -40,5 +40,17 @@ TOOLBAR_BUTTONS=microphone,camera,desktop,raisehand,participants-pane,tileview,s
|
|||
|
||||
JVB_ADVERTISE_IPS={{ visio_ip_publique }}
|
||||
JVB_PORT={{ visio_port_media }}
|
||||
{% if visio_turn_actif | default(false) %}
|
||||
|
||||
# Bloc A (ADR 0006) : relais TURN sur 443 pour les réseaux scolaires qui
|
||||
# bloquent l'UDP. Prosody calcule lui-même, pour chaque participant, un
|
||||
# couple nom d'utilisateur/mot de passe valable 24 h (TURN_CREDENTIALS,
|
||||
# secret partagé avec coturn — dans secrets.env, jamais ici). Lu directement
|
||||
# dans prosody.cfg.lua (confirmé dans le fichier source de l'image, pas
|
||||
# deviné) : aucun module ni plugin personnalisé à écrire.
|
||||
TURNS_HOST={{ visio_domaine_turn }}
|
||||
TURNS_PORT=443
|
||||
TURN_TTL=86400
|
||||
{% endif %}
|
||||
VIDEOBRIDGE_MAX_MEMORY={{ visio_memoire_jvb }}
|
||||
JICOFO_MAX_MEMORY={{ visio_memoire_jicofo }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue