ajoute le proxy console Go et le relais WebSocket vers nova-serialproxy

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-08 09:35:16 +02:00
parent 30e14b1c54
commit 8742bde807
30 changed files with 1103 additions and 6 deletions

View file

@ -314,3 +314,35 @@ Cette étape a un piège intéressant pour la suite : Laravel a deux systèmes d
**Suite**
Étape 9 : proxy console Go et xterm.js (jeton Redis à usage unique, relais WebSocket vers `nova-serialproxy`, lecture seule pour le prof).
## 2026-10-08 — Étape 9 : proxy console Go et xterm.js
**Fait**
- Vérifié avant d'écrire du code : l'appel Nova exact (`POST /servers/{id}/remote-consoles`) et le format d'authentification Keystone par *application credential* (`POST /v3/auth/tokens`, méthode `application_credential`), par observation directe (`openstack --debug`) et un test `curl` réel — pas de mémoire.
- Go 1.27.1 installé localement (Homebrew) pour développer `proxy-console/` sur le Mac, comme pour Laravel à l'étape 8. Déployé en compilant **sur le serveur** à partir du code source (pas de binaire committé), Go 1.27.1 installé là-bas depuis l'archive officielle (somme SHA256 vérifiée via le flux JSON officiel de go.dev, pas depuis un résumé de page).
- Service Go (`proxy-console/`, utilisateur système dédié sans shell, durci avec `ProtectSystem=strict`) : un `Hub` par VM (`instance_id`) maintient une seule connexion amont vers `nova-serialproxy` (elle n'en accepte qu'une), et duplique ce qu'il 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.
- Côté Laravel : `NovaClient` (jeton Keystone mis en cache ~50 min), `JetonConsole` (écrit `console:{jeton}` dans Redis, 5 minutes, format lu par le proxy Go), `ConsoleController` (vérifie que l'élève est bien propriétaire de sa session de lab et admis ; que le prof est bien propriétaire de la séance).
- Nginx : `location /console/` relayée en WebSocket vers `127.0.0.1:8081`.
- Front : nouveau script `console-reelle.js` (vraie connexion WebSocket, xterm.js) — distinct de `paillasse-terminal.js` qui est un faux shell simulé pour la vitrine, explicitement prévu pour être remplacé en production. Intégré dans la salle d'attente élève (une fois admis, si une VM est associée) et dans une nouvelle page prof en lecture seule.
**Problèmes rencontrés et solution**
1. **VM de test créée dans le mauvais projet OpenStack** (`admin` plutôt que `paillasse`) : Nova renvoyait `404 Instance could not be found` à l'*application credential* du portail (scopée à `paillasse`), pas une erreur d'authentification. Recréée avec le cloud `paillasse-service`, comme à l'étape 6.
2. `.env` de production existait déjà depuis l'étape 8 et n'est jamais régénéré : les nouvelles variables `OS_*` devaient être ajoutées sans y retoucher le reste (`APP_KEY` en particulier). Ajouté via des tâches `lineinfile` idempotentes, séparées du template de première génération.
3. `go build`, lancé avec `$HOME` pointé sur le dossier de l'app, y crée `.cache/` et `go/pkg/mod/` appartenant à `root` : le `rsync` suivant (sans `become`) ne pouvait plus les supprimer. Exclus du `rsync`.
4. Premier test manuel de la console : « Invalid WebSocket Header ». Le script de test demandait le sous-protocole `binary`, nécessaire pour parler *directement* à `nova-serialproxy` (étape 6) mais pas à notre propre proxy, qui ne le renvoie pas — un vrai navigateur ne le demande pas non plus. Corrigé dans le script de test ; rien à changer côté proxy ni côté `console-reelle.js`.
**Critère vérifié**
- Une élève fictive a tapé `ip a` dans sa VM et vu le résultat, via le chemin complet navigateur → Nginx → proxy Go → `nova-serialproxy`.
- Un autre élève n'a pas pu obtenir de jeton pour cette session de lab (`403`).
- Vérification allant au-delà du critère minimal : prof et élève connectés en même temps sur la même VM — le prof voit ce que tape l'élève (duplication du flux), et sa propre tentative d'écrire n'apparaît jamais dans le flux renvoyé par la VM (protection côté serveur, pas seulement côté interface).
**Comprendre**
La vraie contrainte de cette étape, c'est qu'une console série ne supporte qu'un seul lecteur à la fois — c'est une caractéristique de QEMU/libvirt, pas un choix arbitraire. Tout l'intérêt du proxy Go est d'être le seul à parler à cette connexion fragile et unique, et de se comporter comme un standard téléphonique pour tout le monde d'autre : il décroche une fois, et fait écouter qui il veut sur la même ligne. C'est aussi pour ça que le jeton que porte le navigateur ne doit jamais révéler l'adresse réelle de la console : s'il fallait donner cette adresse directement à chaque élève, rien n'empêcherait d'en ouvrir une deuxième connexion et de faire planter la première.
**Suite**
Étape 10 : liaison Laravel ↔ OpenStack (client HTTP ou `php-opencloud/openstack`) et modèle Heat du lab 08 — poser la vraie mécanique qu'on a seulement simulée à la main à cette étape (associer une VM à une session de lab).