From 7b0a8583ebbc22ab86b98301aa153956a037924f Mon Sep 17 00:00:00 2001 From: Adam Belkacemi Date: Mon, 12 Oct 2026 16:04:00 +0200 Subject: [PATCH] =?UTF-8?q?=C3=A9crit=20le=20bilan=20de=20mission=20et=20n?= =?UTF-8?q?ote=20le=20maintien=20temporaire=20de=20sudo=20sans=20mot=20de?= =?UTF-8?q?=20passe=20(=C3=A9tape=2016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/bilan.md | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/dette.md | 1 + docs/journal.md | 15 +++++ 3 files changed, 189 insertions(+) create mode 100644 docs/bilan.md diff --git a/docs/bilan.md b/docs/bilan.md new file mode 100644 index 0000000..49ea32c --- /dev/null +++ b/docs/bilan.md @@ -0,0 +1,173 @@ +# Bilan de la mission de déploiement — Syllabus + +Mission : `docs/prompt-deploiement-paillasse.md`, étapes 1 à 16, exécutée du +2026-10-07 au 2026-10-12 sur la Dedibox `163.172.4.195` (Debian 12, Xeon +D-1531, 32 Go, RAID 1). Le détail de chaque étape (commandes, problèmes, +explications) est dans `docs/journal.md` ; ce document en est la synthèse. + +## Ce qui fonctionne, avec la preuve de chaque critère + +| Étape | Critère | Preuve (journal) | +|---|---|---| +| 1. Reconnaissance | Inventaire, KVM, version de Kolla justifiée | `docs/inventaire-serveur.md` ; `/dev/kvm` présent ; Kolla 2025.2, seule série maintenue qui supporte Debian 12 (ADR 0001) | +| 2. Durcissement | Clé OK, mot de passe et root refusés, seuls 22/80/443 | `Permission denied (publickey)` pour le mot de passe et pour root ; `nmap` depuis le Mac : 22 seul ouvert (80/443 autorisés, rien n'écoutait encore) | +| 3. Sauvegardes | Une sauvegarde envoyée, un fichier restauré | `quotidienne-20261007.tar.zst.age` présente sur Dedibackup ; `etc/hostname` restauré dans `/tmp` (`sd-124443`) | +| 4. Réseau OpenStack | Après redémarrage : SSH, `dummy0`, veth, Internet | Redémarrage 11:37, SSH 11:39 ; interfaces et IP présentes ; ping 9.9.9.9 et DNS OK ; RAID `[UU]` | +| 5. Kolla | Services OK, aucune API joignable de l'extérieur | `openstack service list` et `compute service list` OK ; `nmap` sur tous les ports OpenStack + 1-1024 : 22 seul | +| 6. Console série | Invite affichée, commande exécutée | Script WebSocket : `localhost login:`, puis `whoami && hostname` → `eleve`, `localhost` | +| 7. Supervision | Une alerte de test reçue par Adam | Alerte injectée dans Alertmanager ; **reçue par Adam sur ntfy** | +| 8. Portail | Pest OK, élève fictif voit son tableau de bord | 11/11 tests ; `lea.m` connectée voit « Salut Léa » | +| 9. Console web | `ip a` dans le navigateur ; un autre élève refusé | Sortie de `ip a` par navigateur → Nginx → proxy Go → Nova ; autre élève : `403` ; écriture du prof ignorée côté serveur | +| 10. Heat, lab 08 | Stack `CREATE_COMPLETE`, terminaux ouverts | Stack créée en 26 s, 3 VM, topologie conforme au `lab.yaml` ; 3 terminaux ouverts ; expiration automatique vérifiée | +| 11. Stock et correction | < 5 s ; correct → réussi ; incorrect → échoué + message | 4,06 s (mesure pessimiste, voir étape 13) ; « Pas encore réussi » 6/10 avec les messages du lab, puis « Réussi » 10/10 ; 3 essais puis échec visible du prof, vérifiés | +| 12. HTTPS | Certificat valide, parcours prof → élève → lab → correction | Let's Encrypt, TLS 1.2/1.3, HSTS, CSP ; parcours en `https`/`wss` : 4,35 s, 6/10 puis 10/10 ; VM sans IP publique ni sortie | +| 13. Capacité et résistance | Chiffres écrits, restauration, retour seul après redémarrage | `docs/capacite.md` ; « RESTAURATION RÉUSSIE » (17 tables identiques) ; tout revenu seul en 11 min, parcours 10/10 ensuite ; ouverture mesurée à 2,80 / 2,85 / 2,85 s | +| 14. Légal et RGPD | Pages en ligne, purge prouvée par Pest | `/legal`, `/confidentialite` en 200, `/statut` → page de statut ; `PurgeRgpdTest` (5 tests) | +| 15. Visio | Prof + 2 élèves ; main levée → parole ; intrus refusé | Test Playwright à 3 navigateurs + intrus : « CRITÈRE DE L’ÉTAPE 15 : RÉUSSI » ; élève sans parole reste en sourdine ; intrus : « Authentication required » | +| 16. Nettoyage | Proposer le retrait de `90-adam` | Proposé avec ses conséquences ; **Adam a choisi de le garder jusqu'à la fin du bloc A** (voir risques) | + +**État relevé le 2026-10-12 à la fin de la mission** : 0 unité systemd en +échec ; 53 conteneurs, 0 en mauvaise santé ; Nginx, PHP-FPM, Horizon, proxy +console, correcteur et relais d'alertes actifs ; `https://syllabuslab.fr` et +la page de statut en 200 ; certificat valable jusqu'au 2027-01-10 pour +`syllabuslab.fr`, `www`, `status` et `meet` ; une stack prête dans le stock +du lab 08 ; dernière sauvegarde le 2026-10-12 à 03:16, **test de restauration +automatique du 2026-10-12 à 04:30 réussi** ; 47 tests Pest et 4 tests Go. + +## Temps réel passé par étape + +Pas de chronométrage pendant la mission : ce sont des **fenêtres entre +commits**, pauses, attentes des réponses d'Adam et nuits comprises. Elles +majorent le temps de travail réel. + +| Étape | Fenêtre | Durée | Ce qui a pris du temps | +|---|---|---|---| +| 1 | 2026-10-07, jusqu'à 06:54 | — | Accès SSH (passphrase de la clé) | +| 2 | 06:54 → 10:32 | ≈ 3 h 40 | Deux retours arrière du filet anti-coupure, auto-bannissement fail2ban (post-mortem) | +| 3 | 10:32 → 11:08 | ≈ 35 min | Identifiants Dedibackup corrigés par Adam | +| 4 | 11:08 → 11:40 | ≈ 30 min | Redémarrage avec accord | +| 5 | 11:40 → 15:57 | ≈ 4 h 15 | Deux `deploy` (ProxySQL contre MariaDB), tables IPv6 de Docker | +| 6 | 15:57 → 16:24 | ≈ 30 min | — | +| 7 | 16:24 → 21:20 | ≈ 5 h | Attente de l'adresse de notification, configuration oubliée sur le serveur (2 fois) | +| 8 | 21:20 → 10-08 02:01 | ≈ 4 h 40 | Taille du portail, deux guards, déploiement | +| 9 | 02:01 → 09:35 | ≈ 7 h 30 (nuit comprise) | — | +| 10 | 09:35 → 10:14, puis ADR jusqu'à 21:01 | ≈ 40 min + questions | Trust Heat refusé aux application credentials (ADR 0002) | +| 11 | 10-08 → 21:52 | > 1 h 30 (journal) | Trois défauts des étapes 8 et 9 trouvés par le parcours réel | +| 12 | 10-09 09:38 → 15:04 | ≈ 1 h + DNS de `status` | Faille XSS, `/cours` en 500 | +| 13 | 15:04 → 20:12 | ≈ 5 h | Test de capacité, sauvegarde du portail ajoutée, redémarrage | +| 14 | 20:12 → 22:18 | ≈ 2 h | Pages légales réécrites, polices auto-hébergées, retours d'Adam | +| 15 | 10-12 matin → 12:35 | une demi-journée | Pare-feu entre conteneurs (`br_netfilter`), rôles Jitsi | +| 16 et bilan | 10-12 après-midi | ≈ 1 h | — | + +En tout : **six jours calendaires, dont environ quatre jours de travail**. + +## Capacité mesurée + +- **20 VM `eleve.petit` (1 vCPU, 768 Mo) en même temps**, sessions et stock chaud + compris : par exemple **une classe de 20 élèves** sur un lab à une VM, ou + **6 élèves** sur le lab 08 (3 VM chacun). Limite appliquée par Nova + (`reserved_host_memory_mb = 16500`) et par le portail (`LABS_CAPACITE_VM`). +- Pourquoi 20 : la mémoire est la limite (≈ 600 Mo par VM active, ≈ 250 Mo au + repos), pas le CPU (20 vCPU à 100 % → hôte à 89 %, correction en 0,6 s). + 22 avant la visio, 20 depuis qu'1,5 Go est réservé à Jitsi. +- Ouverture d'un lab pris dans le stock : **2,8 s** (critère < 5 s). Lancement + à froid de 20 VM : 53 s en médiane, 75 s au p95. D'où le pré-remplissage + 10 minutes avant une séance. +- Visio : non mesurée au-delà de 3 participants. Une classe de 30 en visio + est à tester au pilote (CPU de JVB, débit sortant). + +Détails : `docs/capacite.md`. + +## Dettes et risques connus + +La liste complète, tenue à jour, est dans `docs/dette.md`. Les risques qui +comptent avant une vraie classe : + +| Risque | Conséquence | Réponse | +|---|---|---| +| **Copie de la clé age non confirmée** | Si le serveur est perdu, aucune sauvegarde n'est déchiffrable | Adam (voir plus bas) | +| **Base OpenStack non sauvegardée** | Perte du serveur → projet, compte, gabarits et stacks à recréer à la main | `kolla-ansible mariadb_backup` dans la sauvegarde quotidienne | +| **Un seul serveur, pas de sonde extérieure** | Si le serveur tombe, aucune alerte, et la classe s'arrête | Sonde externe (à décider et commander par Adam) ; self-healing ne couvre pas la panne totale | +| **Visio sans TURN sur 443** | Sur un réseau scolaire qui bloque l'UDP : ni son ni image | coturn derrière 443 avant le pilote | +| **`sudo` sans mot de passe pour `adam`** | La clé SSH de Claude Code donne un accès root complet | Gardé par choix d'Adam jusqu'à la fin du bloc A ; retrait ensuite (commande prête ci-dessous) | +| Images Kuma non figée, Jitsi sans procédure de mise à jour | Mise à jour non testée au prochain redéploiement | Variables de version dans les rôles | +| Vitrine : Tailwind par CDN, scripts en ligne, Pyodide depuis jsDelivr | CSP de la vitrine moins stricte que celle de l'application | Compilation par Vite avant ouverture publique | +| Peu de vérifications dans un vrai navigateur | Hors visio et `/ide`, les pages n'ont été vérifiées qu'en HTML | Playwright est maintenant installé : l'étendre aux parcours clés | +| Kolla installé depuis une branche | Pas reproductible à l'identique | À régler lors de la montée de version | + +Deux incidents de ma part, déjà traités : un secret d'application credential +de test affiché en clair (credential supprimée aussitôt) ; un déploiement en +échec pris pour un succès (règle depuis : toujours lire `PLAY RECAP`). + +## Ce qu'Adam doit faire lui-même + +**Secrets et sécurité** + +1. **Copier la clé age hors du serveur et le prouver** : procédure dans + `docs/runbooks/sauvegardes.md` (déchiffrer `~/Desktop/test-cle-sauvegardes.age` + avec la copie). Ranger ensuite la copie dans un gestionnaire de mots de passe. +2. **Changer la passphrase SSH** si l'ancienne clé ou la même passphrase sert + ailleurs : elle avait été collée dans la conversation à l'étape 1. +3. **Retirer `/etc/sudoers.d/90-adam` à la fin du bloc A** (la clé de Claude + Code n'a pas de passphrase : ce fichier est la seule barrière entre elle et root). D'abord vérifier son mot de passe dans sa propre session + (`sudo -k && sudo -v`), garder cette session ouverte, puis : + `sudo rm /etc/sudoers.d/90-adam`. Les playbooks se lancent ensuite avec `-K`. +4. Faire tourner le mot de passe du compte `paillasse-service` une fois par an + (`docs/runbooks/compte-service-openstack.md`). + +**Comptes et contenus** + +5. Créer le premier vrai compte prof (le sien) sur `https://syllabuslab.fr/inscription` + et une vraie classe. +6. Compléter les CGU (article « Disponibilité et responsabilité ») et indiquer + un DPO s'il en désigne un. +7. Trancher les slogans « du lycée au master » et le sort des 32 pages d'aperçu. +8. Dire si l'ancien `storage/logs/laravel.log` peut être supprimé. + +**Tests avec un prof** + +9. Faire jouer une séance complète par un collègue prof, sur un vrai réseau + d'établissement : inscription des élèves (fiches et code de classe), + séance planifiée, lab 08, correction, visio (son et image à travers le + filtrage de l'établissement, c'est là que l'absence de TURN se verra), + archivage de la classe. +10. Vérifier dans son navigateur : `/ide` (Python et SQL), une console de lab, + la visio sur Chromebook ou vieux PC. + +**Décisions et achats (jamais par Claude)** + +11. Sonde de supervision hors du serveur (petite VM ailleurs, ou service + externe) et nom DNS `turn.syllabuslab.fr` pour la visio. + +## Prochaines étapes du bloc A + +### 1. Self-healing et post-mortem (semaine 6 du CLAUDE.md) + +- Détecter et relancer : VM figée (l'agent invité ne répond plus → `nova + reboot --hard`), stack du stock en erreur (supprimée et recréée : déjà en + partie fait par le ménage du stock), service arrêté (`Restart=on-failure` sur + toutes les unités maison, à vérifier une par une), conteneur Kolla arrêté. +- Une panne provoquée par cas (`kill -STOP` du QEMU d'une VM du stock, arrêt + du correcteur, arrêt de `nova_compute`), réparée sans intervention, chacune + suivie d'un post-mortem dans `docs/postmortems/`. +- Avant : sauvegarde de la base OpenStack, sinon une réparation ratée n'a pas + de filet. + +### 2. Montée de version d'OpenStack (semaine 7) + +- Passer de la branche `stable/2025.2` à un tag (version figée), puis préparer + la série suivante. Attention : 2026.1 ne supporte plus Debian 12. Une montée + de version d'OpenStack passe donc d'abord par **Debian 13** (montée de + version de l'OS sur un serveur unique, à préparer avec le mode rescue). +- Runbook : sauvegarde des bases, `kolla-ansible upgrade` en service systemd, + vérifications (`capacite.py` réduit, `parcours-lab.py`, `visio-e2e.mjs`), + retour arrière. Retour d'expérience dans le journal. + +### 3. Pilote avec une vraie classe (semaine 8) + +- Prérequis : TURN 443, base OpenStack sauvegardée, clé age confirmée, sonde + extérieure, mentions légales complètes, test avec un collègue (point 9). +- Pendant : relever le temps d'ouverture réel (p95), la mémoire, la charge de + la visio, les corrections réussies et échouées, les mains levées. +- Après : retours des élèves et du prof, métriques dans `docs/capacite.md`, + liste des priorités du bloc B. diff --git a/docs/dette.md b/docs/dette.md index 2d25d29..018b80e 100644 --- a/docs/dette.md +++ b/docs/dette.md @@ -26,6 +26,7 @@ retirer la ligne (et le noter dans le journal) quand c'est fait. | Pages d'aperçu publiques (32) | Signalées comme aperçus avec données fictives ; à retirer ou remplacer au fur et à mesure que les fonctions réelles arrivent. | Au fil du bloc B | Étape 14 | | Invitation des élèves majeurs par e-mail (V2 du CLAUDE.md) | Demande un service d'envoi d'e-mails (compte ouvert par Adam, DNS SPF/DKIM). Jamais pour les mineurs. | V2 | 2026-10-09 | | Code de classe : caractères ambigus | Généré avec `Str::random` (0/O, 1/I/l possibles), contrairement aux codes élèves. Utiliser le même alphabet sans ambiguïté. | Bloc B | 2026-10-09 | +| **`sudo` sans mot de passe pour `adam`** (`/etc/sudoers.d/90-adam`) | La clé SSH de Claude Code (sans passphrase) donne un accès root complet. Retrait proposé à l'étape 16 ; gardé par choix d'Adam pendant le bloc A. Retrait : vérifier son mot de passe (`sudo -k && sudo -v`), garder la session ouverte, `sudo rm /etc/sudoers.d/90-adam`, puis playbooks avec `-K`. | Fin du bloc A | Étape 16 | | **Visio : pas de relais TURN sur 443** | Les réseaux scolaires qui bloquent l'UDP n'auront ni son ni image (CLAUDE.md : tout par 443). Restructurer l'entrée HTTPS (aiguillage Nginx par nom) + coturn ; demande un nom DNS (turn.syllabuslab.fr). | Avant le pilote | Étape 15 | | Visio : chat activé à chaud non vérifié | L'interrupteur « Chat des élèves » change la barre d'outils par `overwriteConfig` ; non vérifié en navigateur. Vue mobile de la maquette non reprise (la page élève est simplement adaptative). | Bloc B | Étape 15 | | Image Jitsi figée sans procédure de mise à jour | `stable-11248` : comme pour Kuma, prévoir la montée de version par Ansible (variable `visio_version`) et rejouer `visio-e2e.mjs`. | Bloc B | Étape 15 | diff --git a/docs/journal.md b/docs/journal.md index 10af656..b9b2afd 100644 --- a/docs/journal.md +++ b/docs/journal.md @@ -671,3 +671,18 @@ Dans une visio, l'image et le son ne sont que la partie visible : la sécurité **Suite** Étape 16 : nettoyage. + +## 2026-10-12 — Étape 16 : nettoyage, et fin de mission + +**Fait** + +- Relevé de `/etc/sudoers.d/90-adam` : `adam ALL=(ALL) NOPASSWD:ALL`. `adam` est aussi dans le groupe `sudo` (`%sudo ALL=(ALL:ALL) ALL`) et a un mot de passe défini : retirer le fichier laisserait sudo, avec mot de passe. Conséquences présentées : `ansible-playbook -K`, plus aucune action root possible pour Claude Code seul, services automatiques non concernés (systemd, pas sudo). +- **Décision d'Adam : garder le fichier jusqu'à la fin du bloc A** (self-healing et montée de version demandent encore beaucoup de travail root). Noté en dette avec la procédure de retrait ; rien n'a été modifié sur le serveur. +- Restes de tests sur le serveur (`/root/capacite-20261009`, `etat-*.txt`, `nft-avant-visio.txt`, `/root/unites-retirees/`) conservés : ce sont des preuves citées dans le journal (« ne supprime rien »). +- `docs/bilan.md` écrit : preuve de chaque critère, temps par étape (fenêtres entre commits), capacité (20 VM), risques, ce qu'Adam doit faire, prochaines étapes du bloc A. + +**État vérifié en fin de mission** : 0 unité en échec, 53 conteneurs sains, site et page de statut en 200, certificat jusqu'au 2027-01-10, test de restauration automatique du 2026-10-12 04:30 réussi. + +**Comprendre** + +Un accès « sudo sans mot de passe » est pratique pour automatiser, mais il transforme toute clé SSH du compte en clé root. Ici, c'est précisément la clé de Claude Code. Le garder pendant le bloc A est un compromis assumé, pas un oubli : il est écrit dans la dette avec sa date de fin et la commande de retrait.