20 KiB
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_claudeavait 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
paillassecréé dans~/.ssh/config(163.172.4.195, utilisateuradam,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
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_askpassabsent). Solution : Adam a retiré la passphrase lui-même avecssh-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/) + playbookansible/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(jailsshd, backendnftablescar pas d'iptablessur l'image),chrony(remplacesystemd-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-runde 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
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
- Bug dans le rôle : le
notifypour rechargersshdmanquait sur la tâche de dépôt de la config SSH →sshdjamais rechargé au 1er essai → le filet de sécurité s'est déclenché (normal, rien de cassé) → corrigé en ajoutant lenotify. - Le test de vérification
nmap -p-(65535 ports) était beaucoup trop lent face à une politiquedrop(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). - 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 = 5de 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) avecfail2ban-client set sshd unbanip. Post-mortem complet :docs/postmortems/2026-10-07-fail2ban-auto-ban.md. Correctif :maxretryrelevé à 10.
Critère vérifié
- Connexion par clé : OK. Mot de passe : refusé (
Permission denied (publickey), sans mention depassworddans les méthodes offertes). Root : refusé.nmap -Pn -T4 --open 163.172.4.195depuis 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/) + playbookansible/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,/rootentar.zst, chiffre en flux avecage, envoie en FTP vers Dedibackup aveclftp, 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
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 parbashsi 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), cartarretire le/initial à la création (etc/..., sans./). Corrigé dans le runbook. etc/os-releasen'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,/rootseulement) — 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/hostnameextrait 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.
2026-10-07 — Étape 4 : réseau pour OpenStack
Fait
- Rôle Ansible
reseau-openstack+ playbookansible/reseau-openstack.yml(qui rejoue aussidurcissementpour le pare-feu et son filet anti-coupure). Appliqué après accord explicite d'Adam, puis redémarrage avec son accord. dummy0: 10.10.0.1/24, futurnetwork_interface/kolla_internal_vip_address(Kolla sans HAProxy).- Paire veth :
veth-ext-ovs(sans IP, futurneutron_external_interface) ↔veth-ext-gw(172.24.4.1/24, passerelle du réseau externe OpenStack). - Persistance par
systemd-networkd, limité à ces trois interfaces :eno1reste sousifupdown, inchangé. net.ipv4.ip_forward=1(/etc/sysctl.d/99-reseau-openstack.conf).- nftables : NAT masquerade de 172.24.4.0/24 vers
eno1(/etc/nftables.d/10-nat-openstack.conf, inclus depuis/etc/nftables.conf). Chaîneforwardresserrée à la demande d'Adam : seulementveth-ext-gw → eno1depuis 172.24.4.0/24, et les réponseseno1 → veth-ext-gw(established,related). Tout le reste est refusé ; rien n'entre depuis Internet vers les réseaux internes.
Règles nftables finales (relues après redémarrage)
chain forward { policy drop;
iifname "veth-ext-gw" oifname "eno1" ip saddr 172.24.4.0/24 accept
iifname "eno1" oifname "veth-ext-gw" ct state established,related accept
}
table ip nat { chain postrouting {
ip saddr 172.24.4.0/24 oifname "eno1" masquerade
} }
Problèmes rencontrés et solution
- 1er lancement :
Destination directory /etc/nftables.d does not exist. Le dossier était créé pardurcissement, qui tourne aprèsreseau-openstack. Échec survenu avant toute modification du pare-feu. Corrigé en créant aussi le dossier dansreseau-openstack. - Un lancement a été bloqué par le garde-fou de Claude Code parce que la règle
forwardavait été modifiée après l'accord d'Adam sans lui être remontrée. On a fait une simulation--check --diff, Adam a validé le diff, puis on a appliqué. - Table
f2b-tablede fail2ban absente après rechargement du pare-feu et après redémarrage : c'est normal, fail2ban 1.0 ne la crée qu'au premier bannissement. Point encore à vérifier :flush rulesetà chaque rechargement efface une table de bannissement déjà créée. fail2ban devrait la recréer au bannissement suivant (vérification automatique de ses actions), mais ce n'est pas encore testé.
Critère vérifié (après redémarrage à 11:37, SSH revenu à 11:39)
- SSH répond,
dummy0et la paire veth sont présents avec leurs IP,ip_forward=1. - Internet joignable depuis le serveur (ping 9.9.9.9, résolution de
deb.debian.org), route par défaut inchangée. - Services actifs : ssh, nftables, fail2ban, chrony, systemd-networkd, timer de sauvegarde. RAID
[UU]surmd0etmd1. nmapdepuis le Mac : seul le port 22 est ouvert.
Comprendre
OpenStack a besoin de deux réseaux en plus de l'accès public : un réseau interne pour que ses services se parlent, et un réseau « externe » pour que les routeurs virtuels puissent sortir. Sur un serveur qui n'a qu'une seule IP publique, mettre Neutron directement sur eno1 reviendrait à lui confier notre seule porte d'entrée. On a donc créé des interfaces virtuelles : dummy0, une carte réseau factice qui porte une adresse privée stable pour les services, et une paire veth, qui fonctionne comme un câble virtuel dont un bout ira à Neutron et l'autre sert de passerelle. Le NAT fait sortir ce réseau externe vers Internet en se faisant passer pour l'IP publique. Le pare-feu n'autorise que ce sens-là, plus les réponses, si bien que personne ne peut entrer depuis Internet vers les réseaux internes. Le redémarrage prouve que tout cela est persistant, et pas seulement appliqué en mémoire.
Suite
Étape 5 : Kolla-Ansible 2025.2 tout-en-un. bootstrap-servers peut toucher au pare-feu et à Docker : vérifier avant, rejouer durcissement après, et demander l'accord d'Adam pour ce qui touche au pare-feu.
2026-10-07 — Étape 5 : OpenStack avec Kolla-Ansible 2025.2
Fait
- Rôle Ansible
kolla+ playbookansible/kolla.yml: dépendances système, venv/opt/kolla-venv(Kolla-Ansiblestable/2025.2, version21.3.1.dev4, ansible-core 2.19, clientopenstack8.2.0, SDKdockeretdbus-python),/etc/kolla/globals.ymletall-in-onecopiés depuis le dépôt (kolla/),passwords.ymlgénéré parkolla-genpwdsur le serveur uniquement (600 root:root, jamais dans Git),install-deps. kolla/globals.yml: images Debian bookworm (tag vérifié sur quay.io), API surdummy0, réseau externeveth-ext-ovs, OVN, sans HAProxy, keepalived, ProxySQL, Horizon ni Cinder ; avec Heat, console série, KVM, Prometheus, Grafana.disable_firewall: false. Docker sans iptables ni ip6tables.- Avant de lancer
bootstrap-servers, j'ai lu son code installé localement pour savoir s'il touchait au pare-feu. Sur Debian, il ne fait qu'essayer de désactiverufw(absent) et ne touche ni à nftables ni à l'heure. bootstrap-servers,prechecks,deploy(lancé comme servicesystemd-runpour survivre à une coupure SSH, 22 min),post-deploy.- ADR
docs/adr/0001-kolla-2025-2-sur-debian-12.md, runbookdocs/runbooks/openstack.md. - Correction fail2ban validée par Adam à l'étape 4 : le handler
recharger nftablesrelance aussi fail2ban (handlerlisten), et une option-e durcissement_forcer_rechargement=trueforce un rechargement du pare-feu.
Problèmes rencontrés et solution
install-deps:No such file or directory: 'ansible-galaxy'→ lebindu venv doit être dans lePATH.bootstrap-serversa modifié le pare-feu (arrêt et accord d'Adam). Docker 29 a créé des tablesip6 natetip6 filtermalgréiptables: false: depuis Docker 27, l'IPv6 a son propre réglage. Aucun impact sur la sécurité, car notre chaîneforwardendropl'emporte. Correction :docker_custom_config: {ip6tables: false}, puis un rechargement forcé du pare-feu sous filet anti-coupure. J'ai vérifié qu'un redémarrage de Docker ne recrée plus les tables.prechecks:No module named 'docker'→ Kolla utilise le Python du venv ;dockeretdbus-pythonajoutés au venv.- 1er
deployen échec : MariaDBBind on TCP/IP port. Got error: 98: Address already in use. ProxySQL écoute sur la VIP:3306, et sans HAProxy la VIP est l'IP du nœud, donc il occupait le port de MariaDB. Correction :enable_proxysql: "no". Le conteneur et le volumeproxysqlont été supprimés avec l'accord d'Adam. Le volumeproxysql_socketest encore présent (non validé, à supprimer avec son accord). Au 2ᵉdeploy, MariaDB a démarré sans qu'il faille réinitialiser son volume.
Critère vérifié
openstack service list: keystone, glance, nova, neutron, placement, heat, heat-cfn.openstack compute service list: nova-scheduler, nova-conductor, nova-computeup. Agents OVN (Controller Gateway, Metadata) vivants.nmapdepuis le Mac sur les ports OpenStack (5000, 8774, 8775, 8778, 9292, 9696, 6080-6083, 3000, 9090, 9091, 9093, 9100, 3306, 5672, 15672, 11211, 6641, 6642, 16509…) + 1-1024 : seul 22 est ouvert.- Pare-feu identique à la référence ; seule
f2b-table(fail2ban) en plus. - Correction fail2ban testée : bannissement de l'adresse de test
192.0.2.10(plage réservée à la documentation), rechargement forcé du pare-feu ;f2b-tablerecréée et le bannissement toujours en place. Adresse débannie ensuite. - 46 conteneurs ; RAM utilisée 11 Gio sur 31 (19 Gio disponibles) avant toute VM.
Dettes notées
Les bases OVN (corrigé le 2026-10-07 (voir plus bas).ovsdb-server, 6641/6642) écoutent sur0.0.0.0- Kolla est installé depuis une branche, pas un tag : figer la version pour la reproductibilité (ADR 0001).
Correctifs du 2026-10-07 (suite), demandés par Adam
- Volume Docker résiduel
proxysql_socketsupprimé (accord d'Adam), aprèsproxysql_socketlui-même supprimé lors du correctif ProxySQL. - Écoute OVN restreinte à
10.10.0.1. Cause :ansible/roles/ovn-db/tasks/bootstrap-db.ymlde Kolla faitovn-nbctl/ovn-sbctl set-connection ptcp:PORT:0.0.0.0en dur à la création des bases, sans tenir compte deapi_interface. Les ports de cluster (6643/6644) ne sont pas concernés : ils respectent déjàapi_interface_address. Le pare-feu bloquait déjà ces ports depuis Internet (vérifié à l'étape 5), donc pas de faille réelle, mais la bonne pratique de défense en profondeur ne coûte rien à appliquer.- Correctif codé dans
ansible/roles/kolla/tasks/corrections.yml(idempotent : relit l'écoute actuelle avant de la changer, ignoré si OpenStack n'est pas encore déployé), importé à la fin deansible/roles/kolla/tasks/main.yml, tagcorrections. - Vérifié :
ss -tlnpmontre les 4 ports OVN (6641-6644) sur10.10.0.1uniquement ; un second passage du playbook ne rapporte aucun changement (idempotence confirmée) ; les agents OVN et les services Nova restentupaprès coup.
- Correctif codé dans
Comprendre
Kolla-Ansible déploie OpenStack sous forme de conteneurs Docker : chaque service (Keystone, Nova, Neutron…) tourne dans son propre conteneur, configuré par Ansible à partir d'un seul fichier, globals.yml. Les deux incidents de cette étape viennent de valeurs par défaut prévues pour un vrai cluster de plusieurs serveurs. Docker crée des règles de pare-feu IPv6 pour ses réseaux, et ProxySQL suppose une adresse virtuelle distincte de celle du serveur. Sur un seul nœud, ces mécanismes ne servent à rien et se gênent entre eux. La leçon : lire le code de l'outil avant de le lancer, et comparer le pare-feu avant et après, plutôt que de faire confiance aux options. C'est ce qui a permis de repérer les tables Docker en quelques secondes.
Suite
Étape 6 : projet paillasse, utilisateur de service avec application credentials, image paillasse-debian (virt-customize), flavor eleve.petit, première VM et console série par WebSocket.