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)
- Laravel (
App\Services\OpenStack\NovaClient) s'authentifie auprès de Keystone avec l'application credentialportail-syllabus(étape 6), demande à Nova l'adresse de la console série d'une instance (POST /servers/{id}/remote-consoles). App\Services\OpenStack\JetonConsolerange cette adresse dans Redis sous un jeton aléatoire (console:{jeton}, 5 minutes), avec le rôle (ecriturepour l'élève,lecturepour le prof). Le navigateur ne reçoit que le jeton.- Le navigateur ouvre
ws://.../console/{jeton}, relayé par Nginx vers le proxy Go. - 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ôleecriturepeut 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 adans sa VM et vu le résultat (deux interfaces,loetens3), 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).