Paillasse/docs/runbooks/proxy-console.md
Adam Belkacemi 8742bde807 ajoute le proxy console Go et le relais WebSocket vers nova-serialproxy
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-08 09:35:16 +02:00

4.8 KiB

Runbook — Proxy console série (étape 9)

Comment ça marche

Navigateur élève/prof → WebSocket /console/{jeton} → Nginx → proxy-console (Go, 127.0.0.1:8081)
                                                                      │
                                                        lit "console:{jeton}" dans Redis
                                                        (posé par Laravel, 5 min)
                                                                      │
                                                        une seule connexion WebSocket
                                                                      ▼
                                                        nova-serialproxy (10.10.0.1:6083)
  1. Laravel (App\Services\OpenStack\NovaClient) s'authentifie auprès de Keystone avec l'application credential portail-syllabus (étape 6), demande à Nova l'adresse de la console série d'une instance (POST /servers/{id}/remote-consoles).
  2. App\Services\OpenStack\JetonConsole range cette adresse dans Redis sous un jeton aléatoire (console:{jeton}, 5 minutes), avec le rôle (ecriture pour l'élève, lecture pour le prof). Le navigateur ne reçoit que le jeton.
  3. Le navigateur ouvre ws://.../console/{jeton}, relayé par Nginx vers le proxy Go.
  4. Le proxy lit Redis, ouvre (ou réutilise) une seule connexion amont vers nova-serialproxy par VM (instance_id), et duplique ce qu'il en reçoit vers tous les navigateurs abonnés à cette VM. Seul l'abonné en rôle ecriture peut faire passer ses frappes ; les autres sont ignorées côté serveur (pas seulement côté interface).

Le code source : proxy-console/ (Go), portail/app/Services/OpenStack/, portail/app/Http/Controllers/ConsoleController.php.

Déployer

cd ansible && script -q /dev/null ansible-playbook proxy-console.yml -i inventory.ini

Rejoue aussi le rôle portail (le vhost Nginx avec location /console/ et les variables OS_* de .env vivent dans ce rôle).

Où sont les choses

Élément Emplacement
Code source Go /opt/proxy-console/ (utilisateur système dédié proxyconsole, sans shell)
Binaire compilé /opt/proxy-console/proxy-console (compilé sur le serveur, jamais committé)
Secrets du service (mot de passe Redis) /etc/proxy-console.env, 640 root:proxyconsole
Service systemd proxy-console.service, durci (ProtectSystem=strict, NoNewPrivileges)
Scripts de test manuel /opt/construction-image/test-manuel.py, test-duplication.py

Le proxy utilise le même Redis que l'app (port 6380) : pas de deuxième instance à gérer, juste un espace de clés différent (console:*).

Tester manuellement (sans navigateur)

Récupérer un jeton (en tant qu'élève connecté, via l'API du portail), puis :

ssh paillasse
sudo /usr/bin/python3 /opt/construction-image/test-manuel.py 'ws://127.0.0.1:8081/console/<jeton>' 'ip a'

Pour vérifier la duplication (élève + prof simultanés, le prof ne peut rien taper) :

sudo /usr/bin/python3 /opt/construction-image/test-duplication.py \
  'ws://127.0.0.1:8081/console/<jeton-ecriture>' 'ws://127.0.0.1:8081/console/<jeton-lecture>'

Piège vécu le 2026-10-08

La VM de test avait été créée dans le mauvais projet OpenStack (admin au lieu de paillasse) : l'application credential du portail, scopée au projet paillasse, recevait 404 Instance could not be found de Nova — pas un problème d'authentification, juste un projet qui ne voit pas les ressources d'un autre. Toujours créer les VM avec le cloud paillasse-service (étape 6), jamais kolla-admin seul.

go build avec $HOME pointé sur le dossier de l'app crée .cache/ et go/pkg/mod/ appartenant à root, que le rsync suivant (qui tourne sans become) ne peut plus supprimer. Ces deux dossiers sont exclus du rsync.

Le sous-protocole WebSocket binary, nécessaire pour parler directement à nova-serialproxy (étape 6), n'a pas sa place pour parler à notre proxy : le vrai navigateur ne le demande pas, et un client qui le demande sans que le proxy le lui renvoie peut rejeter le handshake (« Invalid WebSocket Header »). Les scripts de test ne le demandent plus.

Vérifié le 2026-10-08

  • Une élève fictive a tapé ip a dans sa VM et vu le résultat (deux interfaces, lo et ens3), via le chemin complet navigateur → Nginx → proxy Go → nova-serialproxy.
  • Un autre élève a tenté d'obtenir un jeton pour cette même session de lab : 403 Forbidden.
  • Prof et élève connectés en même temps sur la même VM : le prof a vu une commande tapée par l'élève (duplication du flux), et sa propre tentative d'écrire n'a eu aucun effet (vérifié qu'elle n'apparaît jamais dans le flux renvoyé par la VM).