# Journal de bord — Syllabus (ex-Paillasse) ## 2026-10-07 — Étape 1 : reconnaissance du serveur **Fait** - Déblocage de l'accès SSH : la clé dédiée `~/.ssh/id_ed25519_claude` avait une passphrase sans invite interactive disponible dans Claude Code. Adam a retiré la passphrase lui-même (`ssh-keygen -p -f ~/.ssh/id_ed25519_claude`, nouvelle passphrase vide). - ⚠️ Incident mineur : au premier essai, Adam a collé l'ancienne passphrase directement dans la conversation avec Claude Code au lieu de la taper dans le prompt du terminal. Elle n'a pas été utilisée ni répétée, et elle est devenue sans objet dès que la passphrase de la clé a été retirée. **Point d'attention pour la suite : ne jamais coller de secret dans le chat, toujours dans le terminal.** - Alias `paillasse` créé dans `~/.ssh/config` (`163.172.4.195`, utilisateur `adam`, `IdentityFile ~/.ssh/id_ed25519_claude`, `IdentitiesOnly yes`). - Reconnaissance en lecture seule via SSH : OS, noyau, CPU, virtualisation, RAM, disques/RAID, réseau, état actuel de SSH et du pare-feu, paquets de durcissement déjà présents ou non. - Vérification dans la documentation officielle OpenStack (support matrix + page des séries) de la version de Kolla-Ansible à utiliser : **2025.2 (Flamingo)**, seule série récente qui supporte encore Debian 12 bookworm et qui est encore maintenue. - Écriture de `docs/inventaire-serveur.md`. **Commandes clés** ```bash ssh -i ~/.ssh/id_ed25519_claude -o IdentitiesOnly=yes adam@163.172.4.195 ... ssh paillasse "lscpu; ls -la /dev/kvm; cat /proc/mdstat; ip -br a; sudo nft list ruleset; sudo sshd -T" ``` **Problèmes rencontrés et solution** - Connexion SSH refusée (`Permission denied (publickey,password)`) : la clé privée avait une passphrase, et l'environnement Claude Code n'a pas de terminal interactif pour la saisir (`ssh_askpass` absent). Solution : Adam a retiré la passphrase lui-même avec `ssh-keygen -p`. **Comprendre** Avant de changer quoi que ce soit, il fallait savoir précisément sur quoi on allait travailler : est-ce que la virtualisation matérielle marche vraiment (sans ça, pas d'OpenStack utilisable), le RAID est-il sain, combien de RAM reste disponible, et surtout quelle version de Kolla-Ansible est compatible avec Debian 12. C'est important car OpenStack évolue vite : la version la plus récente (2026.1) a justement arrêté de supporter Debian 12, donc l'installer aveuglément aurait bloqué tout le projet dès l'étape 5. On a aussi remarqué que le pare-feu est aujourd'hui totalement ouvert et que root peut encore se connecter par clé : ce sont exactement les deux points que l'étape 2 (durcissement) va corriger. Rien n'a été modifié sur le serveur à cette étape, uniquement des commandes de lecture. **Suite** Étape 2 : durcissement SSH + pare-feu nftables + fail2ban/chrony/unattended-upgrades, via un rôle Ansible depuis le Mac. C'est une étape qui touche SSH et le réseau : je vais détailler la commande avant de l'exécuter, prévoir un retour arrière automatique (anti-coupure), et ne redémarrer/couper rien sans l'accord explicite d'Adam pour les points couverts par la règle 3 du prompt de déploiement. ## 2026-10-07 — Étape 2 : durcissement du serveur **Fait** - Rôle Ansible `durcissement` (`ansible/roles/durcissement/`) + playbook `ansible/durcissement.yml`, appliqués après accord explicite d'Adam. - Pare-feu nftables : tout refusé en entrée sauf loopback, trafic établi, ICMP, et les ports 22/80/443. - SSH : `PermitRootLogin no`, `PasswordAuthentication no` (connexion par clé uniquement), via un drop-in `/etc/ssh/sshd_config.d/99-durcissement.conf`. - `fail2ban` (jail `sshd`, backend `nftables` car pas d'`iptables` sur l'image), `chrony` (remplace `systemd-timesyncd`), `unattended-upgrades` (sécurité auto, **redémarrage automatique explicitement désactivé** — un reboot reste une décision d'Adam). - Filet de sécurité anti-coupure : timer `systemd-run` de 5 minutes armé avant toute modification du pare-feu/SSH, script de retour à l'état précédent (`/usr/local/sbin/durcissement-rollback.sh`), désarmé seulement après vérification réussie depuis une connexion SSH toute neuve + `nmap`. - Vérifié deux fois l'idempotence (rejouer le playbook ne change rien d'inattendu). **Commandes clés** ```bash cd ansible && script -q /dev/null ansible-playbook durcissement.yml -i inventory.ini ssh paillasse "sudo systemctl list-timers | grep durcissement" # filet de sécurité nmap -Pn -T4 --open 163.172.4.195 ``` **Problèmes rencontrés et solution** 1. Bug dans le rôle : le `notify` pour recharger `sshd` manquait sur la tâche de dépôt de la config SSH → `sshd` jamais rechargé au 1er essai → le filet de sécurité s'est déclenché (normal, rien de cassé) → corrigé en ajoutant le `notify`. 2. Le test de vérification `nmap -p-` (65535 ports) était beaucoup trop lent face à une politique `drop` (chaque port fermé attend un timeout) → dépassait les 5 minutes du filet de sécurité → 2ᵉ déclenchement automatique du rollback (normal, rien de cassé) → corrigé en limitant le scan aux ports par défaut + `-T4` (4,5 s au lieu de plusieurs minutes). 3. **Incident réel : auto-bannissement par fail2ban.** Les tentatives volontairement ratées (mot de passe, root) de nos propres tests de vérification, répétées sur plusieurs runs rapprochés, ont dépassé le seuil `maxretry = 5` de fail2ban : l'IP du Mac d'Adam s'est fait bannir par son propre pare-feu. Résolu via la console web Scaleway (hors bande) avec `fail2ban-client set sshd unbanip`. Post-mortem complet : `docs/postmortems/2026-10-07-fail2ban-auto-ban.md`. Correctif : `maxretry` relevé à 10. **Critère vérifié** - Connexion par clé : OK. Mot de passe : refusé (`Permission denied (publickey)`, sans mention de `password` dans les méthodes offertes). Root : refusé. `nmap -Pn -T4 --open 163.172.4.195` depuis le Mac : seul le port 22 est ouvert (80/443 autorisés par le pare-feu mais rien n'écoute encore dessus — normal, le portail arrive à l'étape 8). **Comprendre** Durcir un serveur, c'est fondamentalement risqué pour l'accès qu'on a déjà : chaque changement de pare-feu ou de SSH peut potentiellement nous enfermer dehors. D'où le filet de sécurité (un minuteur qui annule tout automatiquement si on ne confirme pas que ça marche dans les 5 minutes) : il s'est déclenché deux fois pendant la mise au point, et les deux fois c'était le bon comportement — un bug ailleurs, pas un défaut du filet. L'incident fail2ban est différent et plus subtil : ce n'est pas le pare-feu qui nous a bloqués, c'est un service de protection qu'on vient d'installer qui a réagi à nos propres tests comme à une attaque. C'est un vrai piège classique en administration système, qui vaut la peine d'être raconté en entretien : on protège un système contre tout le monde, y compris soi-même si on ne fait pas attention au volume de ses propres tentatives. **Suite** Étape 3 : sauvegardes vers Dedibackup (archives `tar.zst` chiffrées `age`, rotation 7 quotidiennes + 4 hebdomadaires, timer systemd). ## 2026-10-07 — Étape 3 : sauvegardes vers Dedibackup **Fait** - Rôle Ansible `sauvegardes` (`ansible/roles/sauvegardes/`) + playbook `ansible/sauvegardes.yml`. Pas de risque réseau/pare-feu/SSH ici, donc appliqué directement sans attendre d'accord explicite (règle 3). - Paire de clés **age** générée sur le serveur (`/root/.age/cle-sauvegardes.txt`, `600`) : la clé privée ne quitte jamais le serveur. - Script `/usr/local/sbin/sauvegarde-dedibackup.sh` : archive `/etc`, `/home`, `/root` en `tar.zst`, chiffre en flux avec `age`, envoie en FTP vers Dedibackup avec `lftp`, duplique en hebdomadaire le dimanche, applique la rotation (7 quotidiennes + 4 hebdomadaires, suppression du surplus sur Dedibackup). - Timer systemd `sauvegarde-dedibackup.timer` : tous les jours à 03:15 (±5 min). - `docs/runbooks/sauvegardes.md` écrit (déclenchement manuel, liste, restauration, rappel sur la clé age). **Commandes clés** ```bash cd ansible && script -q /dev/null ansible-playbook sauvegardes.yml -i inventory.ini ssh paillasse "sudo systemctl start sauvegarde-dedibackup.service" # restauration testée : voir docs/runbooks/sauvegardes.md ``` **Problèmes rencontrés et solution** - 1er envoi FTP : `530 Bad credentials`. Diagnostic sans jamais afficher les identifiants (longueur et forme des variables seulement). Cause probable : valeurs non protégées par des guillemets dans `/root/.dedibackup`, risque de troncature silencieuse par `bash` si le mot de passe contient `#`, `$` ou une espace. **Adam a corrigé le fichier lui-même** (jamais Claude, le fichier contient un secret). Après correction, l'envoi a réussi. - Extraction de test avec un chemin `./etc/...` : échec (`Not found in archive`), car `tar` retire le `/` initial à la création (`etc/...`, sans `./`). Corrigé dans le runbook. - `etc/os-release` n'a pas pu être lu après extraction : c'est un lien symbolique vers `../usr/lib/os-release`, hors du périmètre sauvegardé (`/etc`, `/home`, `/root` seulement) — comportement normal, pas un bug. **Critère vérifié** - Sauvegarde envoyée : `quotidienne-20261007.tar.zst.age`, confirmée présente sur Dedibackup (`lftp cls`). - Fichier restauré avec succès dans `/tmp` : `etc/hostname` extrait et relu, contenu conforme (`sd-124443`). Dossier de test nettoyé après coup. **Comprendre** Chiffrer avant d'envoyer (plutôt que de compter sur un transport chiffré) a un avantage : Dedibackup n'a accès qu'à un FTP en clair, mais comme le contenu est déjà chiffré par `age` avant de partir, intercepter le transfert ne révèle rien d'utile. La contrepartie, c'est qu'il ne faut jamais perdre la clé privée `age` : sans elle, toutes les sauvegardes passées deviennent illisibles pour toujours, même si elles sont bien là sur Dedibackup. D'où le rappel à Adam de garder une copie de cette clé ailleurs que sur le serveur. **⚠️ Reste à faire par Adam** : copier `/root/.age/cle-sauvegardes.txt` en dehors du serveur (gestionnaire de mots de passe, clé USB chiffrée…) — voir `docs/runbooks/sauvegardes.md`. **Suite** Étape 4 : réseau pour OpenStack (`dummy0`, paire veth, NAT). Touche le réseau → accord explicite d'Adam obligatoire avant d'appliquer, filet anti-coupure, et redémarrage du serveur seulement avec son accord.