91 lines
4.8 KiB
Markdown
91 lines
4.8 KiB
Markdown
|
|
# 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).
|