# 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/' '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/' 'ws://127.0.0.1:8081/console/' ``` ## 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).