From 08ba3a7dfd7e30fde32c2553b8887d5385840509 Mon Sep 17 00:00:00 2001 From: Alpinux Date: Sun, 20 Sep 2026 00:13:05 +0200 Subject: [PATCH] Serveur Debian 13 : corriger les commandes fautives, et expliquer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revue croisée avec les guides de référence du domaine. Quatre commandes échouaient ou donnaient l'illusion de fonctionner : - journald : le drop-in était édité avant que son répertoire existe, nano refusait d'enregistrer ; - restic : les variables posées par « export » ne passent pas sudo, qui les efface — le dépôt n'était pas celui qu'on croyait ; - la sauvegarde reposait sur « systemd-run », dont l'unité transitoire disparaît au redémarrage : c'est la sauvegarde qui s'arrête sans que personne ne le voie. Remplacée par un .service + .timer persistant ; - le filet de sécurité du pare-feu n'existait que pour ufw ; l'équivalent nftables manquait. Ajouts de fond, par ordre d'importance : vérifier l'empreinte de la clé d'hôte à la première connexion — le seul moment où une interception est possible —, régénérer les clés des images clonées, AppArmor, l'audit Lynis en fin de parcours, la sonde de supervision externe (une alerte émise par la machine ne dit rien quand la machine est morte), la journalisation distante, le fichier 20auto-upgrades qui commande réellement l'automatisme, needrestart non interactif, growpart, la jail recidive, les clés FIDO2, et AllowGroups à la place d'AllowUsers pour qu'un second administrateur existe. Pédagogie : le modèle de menace en ouverture, un niveau par étape avec un parcours court de quinze minutes, des variables qu'on exporte au lieu de les remplacer à la main, quatre encadrés « pour comprendre » (FQDN/PTR, clé publique, activation par socket, tmpfs), et sur les trois étapes où l'on peut se verrouiller dehors : résultat attendu, si ça échoue, et comment revenir en arrière. Plus une annexe pour le jour où c'est raté — console, mode rescue, chroot. Les avertissements passent en admonitions typées : la couleur distingue enfin l'information du danger. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- docs/technique/serveur-debian-13.md | 850 ++++++++++++++++++++++++++-- 1 file changed, 792 insertions(+), 58 deletions(-) diff --git a/docs/technique/serveur-debian-13.md b/docs/technique/serveur-debian-13.md index 1347978..56d14bd 100644 --- a/docs/technique/serveur-debian-13.md +++ b/docs/technique/serveur-debian-13.md @@ -1,20 +1,69 @@ --- -description: Préparer un serveur Debian 13 « Trixie » livré par un hébergeur — DNS, nom d'hôte, sources deb822, comptes, SSH par clé, pare-feu, fail2ban, mises à jour automatiques, supervision et sauvegarde. +description: Préparer un serveur Debian 13 « Trixie » livré par un hébergeur — DNS, nom d'hôte, sources deb822, comptes, SSH par clé, pare-feu, fail2ban, AppArmor, mises à jour automatiques, supervision, sauvegarde restaurable et audit Lynis. --- # Préparation d'un serveur Debian 13 (Trixie) !!! info "État du document" - Rédigé le **19/09/2026** pour **Debian 13 « Trixie »**. Il remplace l'ancienne fiche - Debian 11 : ce qui a changé est résumé en fin de page. + Rédigé le **19/09/2026** pour **Debian 13 « Trixie »**, révisé le **20/09/2026** après + relecture croisée avec les guides de référence du domaine. Il remplace l'ancienne + fiche Debian 11 : ce qui a changé est résumé en fin de page. ## Avant de commencer -Cette procédure part d'une installation minimale de Debian 13 « Trixie » fraîchement livrée par un hébergeur, et s'arrête au moment où le serveur est prêt à recevoir ses services. Comptez 30 à 45 minutes. +Cette procédure part d'une installation minimale de Debian 13 « Trixie » fraîchement livrée par un hébergeur, et s'arrête au moment où le serveur est prêt à recevoir ses services. -**Règle d'or** : tant que vous touchez à SSH ou au pare-feu, gardez une **deuxième session SSH ouverte** sur le serveur et ne la fermez jamais. Elle est votre filet de sécurité si une modification vous verrouille dehors. +**Comptez une heure** en lisant vraiment, une demi-heure si vous connaissez déjà le +terrain. Rien ici ne se rattrape en vitesse : les quatre étapes qui touchent à l'accès +peuvent vous enfermer dehors. -Vérifiez avant de commencer que vous disposez d'un **accès de secours hors SSH** : console KVM/VNC, console série ou mode rescue de l'hébergeur. Sans lui, une erreur de pare-feu signifie une réinstallation. +### Contre quoi vous vous protégez + +Un serveur qui vient d'être livré n'est pas tranquille : son adresse IP appartient à des +plages publiques que des robots balaient en permanence. Les **premières tentatives de +connexion arrivent dans les minutes qui suivent la mise en ligne**, avant même que vous +ayez fini de le configurer. Elles ne vous visent pas : elles essaient `root`, `admin`, +`test`, avec les mots de passe des fuites connues, sur des millions d'adresses. + +Trois familles d'ennuis, et ce que cette fiche oppose à chacune : + +| Ce qui arrive vraiment | Ce qui l'en empêche | +| --- | --- | +| Un robot devine un mot de passe faible et ouvre une session | Authentification par clé seule, root refusé, comptes nominatifs (étapes 7 à 9) | +| Un service oublié écoute sur le réseau et se fait exploiter | Inventaire de ce qui écoute, pare-feu fermé par défaut, AppArmor (étapes 2, 10, 13) | +| Une faille publiée reste non corrigée pendant des semaines | Correctifs de sécurité automatiques, alerte de fin de support (étape 12) | + +Et quand tout cela échoue quand même : un instantané, une sauvegarde restaurable et des +journaux qui existent ailleurs (étapes 14 et 15). + +!!! danger "Règle d'or : ne fermez jamais la seconde session" + Tant que vous touchez à SSH ou au pare-feu, gardez une **deuxième session SSH + ouverte** sur le serveur. Chaque modification se teste depuis une **troisième** + session, jamais en refermant celle qui marche. + + Vérifiez aussi, avant de commencer, que vous disposez d'un **accès de secours hors + SSH** : console KVM/VNC, console série ou mode rescue de l'hébergeur. Sans lui, une + erreur de pare-feu signifie une réinstallation. En cas de problème, la marche à suivre + est en fin de page : [Si vous êtes verrouillé dehors](#si-vous-etes-verrouille-dehors). + +### Les niveaux, et le parcours court + +Chaque étape porte un niveau : + +| Niveau | Ce que cela veut dire | +| --- | --- | +| **Essentiel** | À faire toujours, même en urgence. Sans cela, la machine est exposée ou inexploitable. | +| **Recommandé** | Ce qui distingue un serveur tenu d'un serveur qui tient par chance. À faire le jour même. | +| **Avancé** | Utile selon l'usage et l'hébergeur. À lire, à appliquer si le contexte s'y prête. | + +**Vous devez sécuriser un serveur ce soir, en un quart d'heure ?** Faites, dans l'ordre : +[2](#etape-2-premiere-connexion) (constat et empreinte), +[4](#etape-4-sources-apt-et-mise-a-jour) (mise à jour), +[7](#etape-7-compte-administrateur-et-sudo) (compte admin), +[8](#etape-8-cles-ssh-et-durcissement) (clés SSH), +[10](#etape-10-pare-feu) (pare-feu), +[12](#etape-12-mises-a-jour-de-securite-automatiques) (mises à jour automatiques). +Le reste peut attendre le lendemain — mais pas la semaine suivante. ### Variables utilisées dans ce document @@ -23,6 +72,7 @@ Remplacez systématiquement ces valeurs par les vôtres. | Variable | Exemple | Signification | | --- | --- | --- | | `SRV` | `web01` | Nom court du serveur (sans domaine) | +| `DOMAINE` | `exemple.fr` | Le domaine qui porte le serveur | | `FQDN` | `web01.exemple.fr` | Nom complet, celui du DNS et du reverse | | `IPV4` | `203.0.113.10` | Adresse IPv4 publique | | `IPV6` | `2001:db8::10` | Adresse IPv6 publique, si fournie | @@ -30,6 +80,27 @@ Remplacez systématiquement ces valeurs par les vôtres. | `VENDOR` | `debian` | Compte créé par l'hébergeur, à neutraliser | | `SSHPORT` | `2222` | Port SSH final, si vous le déplacez | +Plutôt que de remplacer `web01` à la main dans quarante blocs — c'est là que naissent les +erreurs — posez-les une fois en début de session, sur le serveur comme sur votre poste : + +```bash +export SRV=web01 DOMAINE=exemple.fr USER=alix SSHPORT=2222 +export FQDN="$SRV.$DOMAINE" +export IPV4=203.0.113.10 +``` + +Les commandes de cette page s'écrivent alors telles quelles, et `echo $FQDN` vérifie ce +que le shell a vraiment compris. + +!!! warning "Deux pièges des variables d'environnement" + Elles **disparaissent à la déconnexion** : reposez-les à chaque nouvelle session, ou + écrivez-les dans votre `~/.bashrc` le temps de l'installation. + + Et **`sudo` ne les transmet pas** (`env_reset` est le défaut Debian) : une commande + comme `sudo echo $FQDN` fonctionne — le shell remplace avant d'appeler sudo — mais + `sudo sh -c 'echo $FQDN'` renvoie du vide. Utilisez `sudo -E` pour conserver + l'environnement, ou passez par `sudo tee` avec un *heredoc*. + ### Ordre des opérations L'ordre compte : le DNS se propage pendant que vous travaillez, et le pare-feu ne se ferme qu'une fois le nouvel accès SSH prouvé. @@ -47,6 +118,28 @@ flowchart LR ## Étape 1 — DNS direct et reverse DNS +**Recommandé** — le serveur tourne sans, mais ses mails partent en indésirables et ses journaux sont illisibles. + +!!! info "Pour comprendre : FQDN, PTR et FCrDNS" + Le **FQDN** est le nom complet de la machine, `web01.exemple.fr` : un nom court + (`web01`) suivi du domaine. C'est lui, et lui seul, qui identifie le serveur pour le + reste d'Internet. + + L'enregistrement **A** traduit ce nom en adresse IP — c'est l'aller. L'enregistrement + **PTR**, le *reverse*, fait le chemin retour : il traduit l'adresse IP en nom. Les + deux sont gérés à des endroits différents : le A chez le gestionnaire du domaine, le + PTR chez l'hébergeur qui possède l'adresse IP. + + ``` + web01.exemple.fr ──(A)──▶ 203.0.113.10 ──(PTR)──▶ web01.exemple.fr + └──────────── la boucle doit se refermer ────────────┘ + ``` + + Quand la boucle se referme sur le même nom, on parle de **FCrDNS** valide. Les + serveurs de messagerie s'en servent comme premier filtre anti-spam : un expéditeur + dont le reverse ne correspond à rien est rejeté ou classé indésirable avant même + d'être lu. + Faites cette étape en premier : la propagation DNS prend de quelques minutes à quelques heures, et elle tournera pendant que vous configurez le reste. Un reverse DNS cohérent conditionne l'acceptation de vos mails et la lisibilité de vos journaux. ### 1.1 Enregistrements directs (zone DNS du domaine) @@ -79,10 +172,13 @@ dig +short -x 2001:db8::10 Les quatre réponses doivent se refermer sur elles-mêmes : le A pointe vers l'IP, le PTR de cette IP renvoie le même FQDN. C'est ce qu'on appelle un FCrDNS valide (*forward-confirmed reverse DNS*). -> Si `dig` n'est pas installé sur votre poste : `sudo apt install dnsutils` sous Debian/Ubuntu, `brew install bind` sous macOS. +!!! note "Si `dig` manque sur votre poste" + Si `dig` n'est pas installé sur votre poste : `sudo apt install dnsutils` sous Debian/Ubuntu, `brew install bind` sous macOS. ## Étape 2 — Première connexion +**Essentiel** — constater l'état de livraison et valider l'identité de la machine. + Connectez-vous avec le compte fourni par l'hébergeur. Sur les images Debian officielles, c'est `debian` ; d'autres utilisent `admin`, `ubuntu` ou `root`. ```bash @@ -91,6 +187,56 @@ ssh debian@203.0.113.10 Utilisez l'IP et non le FQDN pour cette première connexion : le DNS n'est peut-être pas encore propagé. +### Vérifier l'empreinte avant de répondre « yes » + +SSH affiche l'empreinte de la clé d'hôte et demande confirmation. **C'est le seul moment +de toute la vie du serveur où une interception est possible** : une fois l'empreinte +enregistrée dans votre `known_hosts`, toute substitution déclenche une alerte. Répondre +« yes » sans regarder, c'est accepter n'importe quel serveur qui se présente à cette +adresse. + +Comparez l'empreinte affichée avec celle que l'hébergeur publie — panneau d'administration, +courriel de livraison, ou console KVM : + +```bash +# Sur la console KVM de l'hébergeur, ou après une première connexion de confiance +for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$f"; done +``` + +Les deux empreintes doivent être identiques, caractère pour caractère. Si l'hébergeur ne +publie rien et que vous avez une console, relevez-la là : c'est le seul canal qui ne passe +pas par le réseau que vous cherchez à valider. + +### Régénérer les clés d'hôte d'une image clonée + +Certains fournisseurs distribuent des images où les clés SSH ont été générées **une seule +fois, avant le clonage** : des milliers de machines partagent alors la même clé privée, et +qui possède cette image peut se faire passer pour votre serveur. + +```bash +ls -l --time-style=full-iso /etc/ssh/ssh_host_*_key +``` + +Si la date est antérieure à la livraison de votre machine, régénérez-les : + +```bash +sudo rm /etc/ssh/ssh_host_* +sudo dpkg-reconfigure openssh-server +sudo systemctl restart ssh +``` + +Votre prochaine connexion signalera un changement de clé, ce qui est normal ici : +`ssh-keygen -R 203.0.113.10` sur votre poste, puis revérifiez la nouvelle empreinte. + +Le même raisonnement vaut pour l'identifiant de la machine, qui sert de base au DUID +DHCPv6 et à la corrélation des journaux : + +```bash +cat /etc/machine-id +# S'il est identique sur deux machines issues de la même image : +sudo rm -f /etc/machine-id && sudo systemd-machine-id-setup +``` + ### Vérifier ce qu'on a vraiment reçu Avant toute modification, prenez trois minutes pour constater l'état initial. @@ -105,6 +251,21 @@ sudo systemctl list-units --type=service --state=running sudo ss -tulpn # ce qui écoute déjà sur le réseau ``` +!!! tip "Le disque est-il entier ?" + Beaucoup de VPS démarrent sur une image dont la partition est plus petite que le + volume facturé : `lsblk` montre alors un disque de 80 Go dont la partition n'en fait + que 20. L'espace est payé, mais inutilisable tant qu'on ne l'a pas réclamé. + + ```bash + sudo apt install -y cloud-guest-utils # fournit growpart + sudo growpart /dev/vda 1 # adaptez le disque et le numéro + sudo resize2fs /dev/vda1 # xfs_growfs si le système est en XFS + df -h / + ``` + + Faites-le maintenant, avant d'avoir des données : l'opération est sûre mais se + déroule mieux sur une machine vide. + La dernière commande est la plus instructive : elle montre les services exposés dès la livraison. Tout ce qui écoute sur `0.0.0.0` ou `::` et que vous ne reconnaissez pas mérite une décision explicite — désactiver ou garder. ### Si la connexion est refusée @@ -118,6 +279,8 @@ La dernière commande est la plus instructive : elle montre les services exposé ## Étape 3 — Nom d'hôte et /etc/hosts +**Recommandé** — une identité stable, sur laquelle s'appuient mail, journaux et certificats. + ### 3.1 Définir le nom d'hôte Debian attend ici le **nom court**, pas le FQDN. Le FQDN est reconstitué via `/etc/hosts`. @@ -161,7 +324,8 @@ Certains hébergeurs pré-remplissent la ligne `127.0.1.1` avec le nom d'usine d Les deux formes fonctionnent. Supprimer l'ancien nom est plus propre ; le conserver évite de casser un script de l'hébergeur qui s'y référerait encore. -> ⚠️ Ne touchez jamais à la ligne `127.0.0.1 localhost`. Et n'associez pas le FQDN à l'IP publique dans `/etc/hosts` : sur une machine à IP dynamique ou derrière NAT, cela crée des résolutions fantomatiques. +!!! warning "Ne touchez pas à `localhost`" + Ne touchez jamais à la ligne `127.0.0.1 localhost`. Et n'associez pas le FQDN à l'IP publique dans `/etc/hosts` : sur une machine à IP dynamique ou derrière NAT, cela crée des résolutions fantomatiques. ### 3.3 Vérifier @@ -174,17 +338,21 @@ hostnamectl status Si `hostname -f` répond autre chose que le FQDN attendu, la ligne `127.0.1.1` est mal ordonnée. Le changement est immédiat, mais reconnectez-vous pour que l'invite de commande l'affiche. -> Sur certaines images cloud, `cloud-init` réécrit le nom d'hôte à chaque redémarrage. Si votre nom revient à sa valeur d'origine après un `reboot` : -> -> ```bash -> sudo sed -i 's/^preserve_hostname:.*/preserve_hostname: true/' /etc/cloud/cloud.cfg -> grep preserve_hostname /etc/cloud/cloud.cfg -> ``` -> -> Ajoutez la ligne si elle est absente. +!!! warning "Le nom d'hôte revient après un redémarrage ?" + Sur certaines images cloud, `cloud-init` le réécrit à chaque démarrage. Dites-lui de + respecter le vôtre : + + ```bash + sudo sed -i 's/^preserve_hostname:.*/preserve_hostname: true/' /etc/cloud/cloud.cfg + grep preserve_hostname /etc/cloud/cloud.cfg + ``` + + Ajoutez la ligne si elle est absente. ## Étape 4 — Sources APT et mise à jour +**Essentiel** — une machine livrée n'est jamais à jour. + ### 4.1 Passer les sources au format deb822 Debian 13 introduit le format deb822 pour les dépôts. L'ancien format une-ligne est officiellement déprécié, même s'il restera supporté par Debian 14 « Forky » ([détail du changement](https://dev.to/r3m8/debian-13-trixie-adoptez-le-nouveau-format-deb822-1ml6)). Autant partir sur le bon format tout de suite. @@ -217,7 +385,8 @@ Components: main contrib non-free-firmware Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg ``` -> Si le dépôt `trixie-security` manque, ajoutez-le à la main : sans lui, vous ne recevez aucun correctif de sécurité. C'est l'erreur la plus coûteuse de toute cette procédure. +!!! danger "Sans `trixie-security`, aucun correctif" + Si le dépôt `trixie-security` manque, ajoutez-le à la main : sans lui, vous ne recevez aucun correctif de sécurité. C'est l'erreur la plus coûteuse de toute cette procédure. `non-free-firmware` est nécessaire sur matériel physique (cartes réseau, RAID). `contrib` et `non-free` sont optionnels ; ne les ajoutez que si un paquet précis l'exige. @@ -251,6 +420,8 @@ Un état `degraded` signale au moins un service en échec ; `systemctl --failed` ## Étape 5 — Paquets de base +**Recommandé** — de quoi diagnostiquer sans improviser. + ```bash sudo apt install -y \ vnstat iftop htop nano git \ @@ -290,6 +461,8 @@ sudo apt-mark showmanual | head -50 # ce qui a été installé explicitement ## Étape 6 — Locale, fuseau horaire et clavier +**Recommandé** — une heure juste est la condition d'un journal exploitable. + ### 6.1 Locale Attention à l'orthographe : c'est `fr_FR.UTF-8` avec un **tiret**, pas `fr_FR.UTF_8`. Un underscore produit une locale invalide, silencieusement ignorée. @@ -311,7 +484,8 @@ sudo rm -f /etc/locale.gen sudo dpkg-reconfigure --frontend=noninteractive locales ``` -> `LANG=fr_FR.UTF-8` seul dans un terminal ne change que la session en cours. `localectl` écrit dans `/etc/default/locale`, lu par tous les services et toutes les futures connexions. +!!! note "Pourquoi `localectl` et pas `LANG=`" + `LANG=fr_FR.UTF-8` seul dans un terminal ne change que la session en cours. `localectl` écrit dans `/etc/default/locale`, lu par tous les services et toutes les futures connexions. **Recommandation** : gardez les **messages en anglais** même avec une interface en français. Les messages d'erreur anglais sont cherchables et correspondent à la documentation ; les scripts qui parsent une sortie de commande cassent en français. @@ -333,7 +507,8 @@ sudo timedatectl set-timezone Europe/Paris timedatectl list-timezones | grep Paris # pour trouver un autre fuseau ``` -> Sur un parc international ou pour simplifier la lecture des journaux entre machines, `UTC` reste un choix défendable. Le seul vrai critère : que toutes vos machines aient **le même** fuseau. +!!! note "UTC ou heure locale ?" + Sur un parc international ou pour simplifier la lecture des journaux entre machines, `UTC` reste un choix défendable. Le seul vrai critère : que toutes vos machines aient **le même** fuseau. ### 6.3 Synchronisation de l'heure @@ -367,6 +542,8 @@ sudo localectl set-x11-keymap fr ## Étape 7 — Compte administrateur et sudo +**Essentiel** — ne plus travailler avec le compte du fournisseur. + ### 7.1 Créer l'utilisateur ```bash @@ -384,7 +561,8 @@ sudo usermod -aG sudo alix id alix # vérifie que "sudo" apparaît dans les groupes ``` -> Le `-a` de `usermod -aG` est obligatoire : sans lui, l'utilisateur est **retiré de tous ses autres groupes**. C'est une erreur classique et difficile à diagnostiquer. +!!! danger "Le `-a` n'est pas facultatif" + Le `-a` de `usermod -aG` est obligatoire : sans lui, l'utilisateur est **retiré de tous ses autres groupes**. C'est une erreur classique et difficile à diagnostiquer. Si vous avez besoin d'une règle spécifique — par exemple autoriser un redémarrage de service sans mot de passe — n'éditez **jamais** `/etc/sudoers` directement. Créez un fichier dédié dans `/etc/sudoers.d/`, ce qui isole la panne en cas d'erreur de syntaxe : @@ -402,9 +580,10 @@ sudo chmod 0440 /etc/sudoers.d/10-alix sudo visudo -c # contrôle global de la syntaxe ``` -> ⚠️ `visudo` valide la syntaxe avant d'écrire, ce que `nano /etc/sudoers` ne fait pas. Une erreur dans les sudoers non validée rend `sudo` inutilisable pour tout le monde, y compris vous. -> -> Évitez `NOPASSWD: ALL` : cela transforme toute exécution de code sous votre compte en accès root immédiat. +!!! danger "Toujours `visudo`, jamais l'éditeur seul" + `visudo` valide la syntaxe avant d'écrire, ce que `nano /etc/sudoers` ne fait pas. Une erreur dans les sudoers non validée rend `sudo` inutilisable pour tout le monde, y compris vous. + + Évitez `NOPASSWD: ALL` : cela transforme toute exécution de code sous votre compte en accès root immédiat. ### 7.3 Tester avant de couper quoi que ce soit @@ -431,10 +610,19 @@ Host web01 EOF ``` -Vous vous connecterez ensuite avec `ssh web01`. Adaptez `Port` après l'étape 8 si vous le déplacez. +Vous vous connecterez ensuite avec `ssh web01`. + +!!! note "Le port change en cours de route" + À ce stade, SSH écoute encore sur **22** : c'est le port à écrire ici. Si vous le + déplacez à l'étape 8.4, revenez mettre `$SSHPORT` — la suite de cette page emploie + `2222` comme exemple de port final, y compris dans les règles de pare-feu de l'étape + 10. Tant que la bascule n'est pas faite et **vérifiée**, gardez les deux ports + ouverts. ## Étape 8 — Clés SSH et durcissement +**Essentiel** — c'est l'étape qui ferme la porte la plus attaquée. + C'est l'étape la plus risquée. Gardez une session ouverte du début à la fin. ### 8.1 Créer une paire de clés (sur votre poste, pas sur le serveur) @@ -445,6 +633,30 @@ ssh-keygen -t ed25519 -C "alix@poste-bureau" -f ~/.ssh/id_ed25519 Ed25519 est le bon choix par défaut : court, rapide, sûr. Mettez une passphrase — elle protège la clé si votre poste est compromis, et `ssh-agent` évite de la retaper. +!!! info "Pour comprendre : pourquoi une clé plutôt qu'un mot de passe" + `ssh-keygen` produit deux fichiers : une **clé privée**, qui ne quitte jamais votre + poste, et une **clé publique**, que vous déposez sur tous les serveurs où vous allez. + À la connexion, le serveur envoie un défi que seule la clé privée sait résoudre — la + clé privée elle-même ne transite jamais. + + Un mot de passe de douze caractères se devine en quelques milliards d'essais, et les + robots qui scannent le port 22 en font des milliers par heure. Une clé Ed25519 ne se + devine pas : il n'y a rien à essayer. C'est pour cela que l'on peut couper + l'authentification par mot de passe sans rien perdre. + +!!! tip "Option avancée : une clé sur support matériel" + Avec une clé de sécurité FIDO2 (YubiKey, Nitrokey, Token2), le secret ne quitte jamais + le support physique — voler votre ordinateur ne suffit plus, il faut aussi la clé + dans votre poche : + + ```bash + ssh-keygen -t ed25519-sk -C "alix@yubikey" + ``` + + Le suffixe `-sk` (*security key*) demande une confirmation physique à chaque + connexion. Gardez une seconde clé matérielle, ou une clé logicielle de secours dans + `authorized_keys` : une clé FIDO2 perdue ne se duplique pas. + ### 8.2 Déposer la clé publique ```bash @@ -464,6 +676,23 @@ Testez dans une **nouvelle** session : `ssh alix@203.0.113.10` doit passer sans Debian 13 lit `/etc/ssh/sshd_config.d/*.conf`. Écrivez votre configuration là plutôt que dans `sshd_config` : vos choix survivent aux mises à jour du paquet et se retirent en supprimant un seul fichier. +Regardez d'abord ce qui s'y trouve déjà : + +```bash +ls -l /etc/ssh/sshd_config.d/ +``` + +!!! warning "Le premier fichier qui parle a raison" + Pour une directive donnée, **la première valeur rencontrée l'emporte**, et les + fichiers sont lus dans l'ordre alphabétique. Sous Debian, l'`Include` est en tête de + `sshd_config`, donc n'importe quel drop-in gagne contre le corps du fichier — mais un + `50-cloud-init.conf`, fréquent sur les images d'hébergeurs, gagne contre votre + `99-durcissement.conf`. Or il force souvent `PasswordAuthentication yes`. + + Si un tel fichier est présent : nommez le vôtre `00-durcissement.conf`, ou corrigez + directement le fichier fautif. Dans tous les cas, c'est `sshd -T` qui tranche, pas la + lecture des fichiers. + ```bash sudo nano /etc/ssh/sshd_config.d/99-durcissement.conf ``` @@ -480,7 +709,7 @@ MaxSessions 5 LoginGraceTime 30 # Limiter qui peut se connecter -AllowUsers alix +AllowGroups ssh-users # Réduire la surface X11Forwarding no @@ -494,15 +723,36 @@ ClientAliveInterval 300 ClientAliveCountMax 2 ``` -> `AllowUsers alix` remplace avantageusement un `DenyUsers debian` : la liste blanche est explicite et couvre aussi les comptes que vous n'avez pas anticipés. Pensez à l'étendre si vous ajoutez un administrateur. +Le groupe doit exister **avant** le rechargement, sans quoi plus personne ne peut se +connecter : -> `PasswordAuthentication no` vous verrouille dehors si la clé de 8.2 ne fonctionne pas. Ne l'activez qu'après avoir vérifié la connexion par clé. +```bash +sudo groupadd -f ssh-users +sudo usermod -aG ssh-users alix +id alix # ssh-users doit apparaître +``` -Deux précautions : le premier fichier qui définit une directive gagne, et `sshd_config` charge souvent ses drop-ins **en première ligne**. Vérifiez donc la configuration réellement appliquée, jamais le fichier seul. +!!! note "Pourquoi un groupe plutôt qu'une liste de noms" + Une liste blanche vaut mieux qu'un `DenyUsers debian` : elle couvre aussi les comptes + que vous n'avez pas anticipés. Mais `AllowUsers alix` désigne une personne, et un + serveur dont une seule personne détient l'accès est un serveur perdu le jour où cette + personne ne répond plus. + + Avec un groupe, ouvrir l'accès à quelqu'un devient `sudo usermod -aG ssh-users bea`, + et le fermer `sudo gpasswd -d bea ssh-users` — sans jamais rouvrir la configuration de + sshd. **Prévoyez un second administrateur** dès maintenant : c'est le moment où cela + coûte le moins cher. + +!!! danger "L'ordre des opérations n'est pas négociable" + `PasswordAuthentication no` vous verrouille dehors si la clé de l'étape 8.2 ne + fonctionne pas. Vérifiez la connexion par clé dans une session séparée **avant** de + recharger sshd, et gardez la session courante ouverte jusqu'au succès du test. + +Vérifiez maintenant la configuration **réellement appliquée**, jamais le fichier seul : ```bash sudo sshd -t # syntaxe ; silence = OK -sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers|^port' +sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowgroups|^port' ``` Puis rechargez et testez depuis une nouvelle session : @@ -515,6 +765,18 @@ sudo systemctl reload ssh Depuis Debian 12, SSH est démarré par **activation à la demande via systemd** (`ssh.socket`). Conséquence : une directive `Port` dans `sshd_config` **peut rester sans effet**, car c'est le socket systemd qui décide de l'écoute. +!!! info "Pour comprendre : l'activation par socket" + Dans le modèle classique, `sshd` démarre au boot, ouvre lui-même le port 22 et attend. + Avec l'activation par socket, c'est **systemd** qui ouvre le port et écoute à sa + place ; `sshd` ne démarre qu'à l'arrivée d'une connexion, et systemd lui passe la + main. + + Le port n'appartient donc plus à `sshd` mais à l'unité `ssh.socket`. C'est pourquoi + `Port 2222` dans `sshd_config` est lu, accepté, et sans effet : au moment où `sshd` + s'exécute, l'écoute est déjà établie par quelqu'un d'autre. Le gain — quelques + mégaoctets de mémoire tant que personne ne se connecte — explique ce choix par + défaut ; c'est aussi le piège le plus déroutant de Debian 13. + D'abord, constatez votre situation : ```bash @@ -565,15 +827,44 @@ ssh -p 2222 debian@web01.exemple.fr # doit être refusé ssh -p 2222 root@web01.exemple.fr # doit être refusé ``` +**Résultat attendu** — la première commande ouvre une session sans demander de mot de +passe ; les deux autres échouent en `Permission denied (publickey)`. Et la configuration +appliquée confirme les quatre directives qui comptent : + +```bash +sudo sshd -T | grep -Ei '^port|permitrootlogin|passwordauthentication|allowgroups' +``` + +**Si ça échoue** + +| Symptôme | Cause la plus fréquente | +| --- | --- | +| `Permission denied (publickey)` sur **votre** compte | La clé n'est pas dans `authorized_keys`, ou les droits sont trop larges (`chmod 700 ~/.ssh`, `600 authorized_keys`) | +| `Connection refused` après un changement de port | `ssh.socket` n'a pas été rechargé, ou le pare-feu n'ouvre pas le nouveau port | +| Le mot de passe est toujours demandé | Un drop-in antérieur gagne : relisez `ls /etc/ssh/sshd_config.d/` et tranchez avec `sshd -T` | +| Plus personne ne peut entrer | `AllowGroups` pointe un groupe vide ou inexistant | + +**Revenir en arrière**, depuis la session restée ouverte : + +```bash +sudo rm /etc/ssh/sshd_config.d/99-durcissement.conf +sudo systemctl reload ssh +# Si le port avait été déplacé : +sudo rm -f /etc/systemd/system/ssh.socket.d/override.conf +sudo systemctl daemon-reload && sudo systemctl restart ssh.socket +``` + ## Étape 9 — Neutraliser le compte fournisseur +**Essentiel** — un compte connu de tous, au nom prévisible. + Le compte `debian` livré par l'hébergeur est connu de tous et ciblé en premier par les scans. Une fois votre accès personnel prouvé, retirez-lui l'accès SSH. ### 9.1 Choisir le niveau de neutralisation | Niveau | Commande | Effet | Quand | | --- | --- | --- | --- | -| SSH bloqué | `AllowUsers alix` (étape 8.3) | Le compte existe, ne peut plus se connecter par SSH | Cas général | +| SSH bloqué | `AllowGroups ssh-users` (étape 8.3) | Le compte existe, ne peut plus se connecter par SSH | Cas général | | Mot de passe verrouillé | `sudo passwd -l debian` | Plus d'authentification par mot de passe | À combiner avec le précédent | | Shell retiré | `sudo usermod -s /usr/sbin/nologin debian` | Plus aucune session interactive | Compte purement technique | | Compte supprimé | `sudo deluser --remove-home debian` | Disparu | Seulement si rien ne dépend de lui | @@ -586,7 +877,8 @@ sudo usermod -s /usr/sbin/nologin debian sudo rm -f /home/debian/.ssh/authorized_keys ``` -> ⚠️ Ne supprimez pas le compte avant d'avoir vérifié qu'aucun processus ne tourne sous son identité (`ps -u debian`) et qu'il n'est pas utilisé par l'outillage de l'hébergeur (agent de sauvegarde, `cloud-init`, console de secours). Certains hébergeurs recréent le compte à chaque redémarrage. +!!! warning "Ne supprimez pas le compte du fournisseur trop vite" + Ne supprimez pas le compte avant d'avoir vérifié qu'aucun processus ne tourne sous son identité (`ps -u debian`) et qu'il n'est pas utilisé par l'outillage de l'hébergeur (agent de sauvegarde, `cloud-init`, console de secours). Certains hébergeurs recréent le compte à chaque redémarrage. ### 9.2 Le cas de root @@ -597,7 +889,8 @@ sudo rm -f /root/.ssh/authorized_keys Sur Debian, root n'a généralement pas de mot de passe utilisable en SSH dès lors que `PermitRootLogin no` est posé. Le verrouillage est une ceinture en plus des bretelles. -> N'utilisez pas `usermod -s /usr/sbin/nologin root` : vous perdriez l'accès par la console de secours de l'hébergeur, votre dernier recours. +!!! danger "Ne retirez pas le shell de root" + N'utilisez pas `usermod -s /usr/sbin/nologin root` : vous perdriez l'accès par la console de secours de l'hébergeur, votre dernier recours. ### 9.3 Inventaire des comptes @@ -619,6 +912,8 @@ La dernière commande mérite un passage attentif : une clé publique inconnue d ## Étape 10 — Pare-feu +**Essentiel** — tout ce qui écoute est joignable tant que rien ne filtre. + Debian 13 utilise **nftables** comme moteur de filtrage. Deux approches : `nftables` directement, ou `ufw` qui l'utilise en coulisse avec une syntaxe plus simple. ### 10.1 Option A — ufw (recommandé si vous débutez) @@ -631,7 +926,9 @@ sudo ufw default deny incoming sudo ufw default allow outgoing # SSH EN PREMIER — sinon vous vous coupez l'accès à l'activation -sudo ufw limit 2222/tcp comment 'SSH' +sudo ufw limit 2222/tcp comment 'SSH' # $SSHPORT : le port réellement en écoute +# Pendant une bascule de port, autorisez les deux le temps de vérifier : +# sudo ufw limit 22/tcp comment 'SSH ancien port' # Services web, à n'ouvrir que le jour où vous les déployez # sudo ufw allow 80/tcp comment 'HTTP' @@ -688,12 +985,15 @@ sudo systemctl enable --now nftables sudo nft list ruleset ``` -> ⚠️ Ne laissez jamais `policy drop` sur `input` sans règle SSH au-dessus. Testez toujours avec `nft -c -f` avant d'appliquer. +!!! danger "Jamais de `policy drop` sans règle SSH" + Ne laissez jamais `policy drop` sur `input` sans règle SSH au-dessus. Testez toujours avec `nft -c -f` avant d'appliquer. ### 10.3 Filet de sécurité pendant les tests Programmez une désactivation automatique du pare-feu dans 10 minutes. Si vous vous verrouillez dehors, l'accès revient seul ; si tout va bien, vous annulez la tâche. +**Si vous avez choisi ufw :** + ```bash # Avant d'activer les règles sudo systemd-run --on-active=10min --unit=ufw-panic systemctl stop ufw @@ -702,6 +1002,19 @@ sudo systemd-run --on-active=10min --unit=ufw-panic systemctl stop ufw sudo systemctl stop ufw-panic.timer ``` +**Si vous avez choisi nftables** — `systemctl stop ufw` n'existe pas, il faut vider le +jeu de règles : + +```bash +# Avant d'activer les règles +sudo systemd-run --on-active=10min --unit=nft-panic nft flush ruleset + +# Une fois la connexion reconfirmée depuis une nouvelle session +sudo systemctl stop nft-panic.timer +``` + +Ici, l'unité transitoire est exactement ce qu'on veut : elle doit disparaître d'elle-même. + ### 10.4 Le pare-feu de l'hébergeur Beaucoup d'hébergeurs interposent leur propre filtrage en amont (*security group*, « firewall network »). Un port ouvert sur la machine et fermé chez eux reste inaccessible — et l'inverse est vrai. Tenez les deux couches alignées, et documentez-les ensemble. @@ -711,8 +1024,43 @@ Beaucoup d'hébergeurs interposent leur propre filtrage en amont (*security grou nmap -Pn -p- web01.exemple.fr ``` +### 10.5 Contrôler, réparer, annuler + +**Résultat attendu** — la politique d'entrée est en refus, SSH est la seule exception, et +une nouvelle session s'ouvre toujours : + +```bash +sudo ufw status verbose # ou : sudo nft list ruleset +ss -tlpn # ce qui écoute, à comparer avec ce qui est ouvert +``` + +**Si ça échoue** + +| Symptôme | Cause la plus fréquente | +| --- | --- | +| Session en cours figée juste après l'activation | Règle SSH absente ou placée après la politique de refus | +| Le port est ouvert, le service reste injoignable | Le pare-feu de l'hébergeur ne l'ouvre pas de son côté (§10.4) | +| `ufw` semble sans effet | Des règles nftables concurrentes existent : `sudo nft list ruleset` pour voir qui filtre réellement | +| Tout tombe après un redémarrage | Le pare-feu n'est pas activé au démarrage : `systemctl is-enabled ufw` ou `nftables` | + +**Revenir en arrière** — depuis la session restée ouverte, ou par la console de +l'hébergeur : + +```bash +sudo ufw disable # option A +sudo nft flush ruleset # option B, vide tout le filtrage +sudo systemctl disable --now nftables # et empêche son retour au redémarrage +``` + +!!! danger "Un pare-feu vidé est un serveur nu" + `nft flush ruleset` laisse la machine sans aucun filtrage. C'est une manœuvre de + dépannage, pas un état dans lequel on laisse un serveur : remettez les règles dans la + foulée, une fois l'accès retrouvé. + ## Étape 11 — fail2ban +**Recommandé** — écarte le bruit de fond avant qu'il ne remplisse vos journaux. + fail2ban lit les journaux et bannit temporairement les IP qui échouent trop souvent. Avec `PasswordAuthentication no`, son utilité sur SSH est surtout de nettoyer les journaux ; elle redevient essentielle dès que vous exposez un service authentifié par mot de passe. ```bash @@ -741,11 +1089,23 @@ ignoreip = 127.0.0.1/8 ::1 198.51.100.7 enabled = true port = 2222 mode = aggressive + +# Bannit longuement ceux qui reviennent après chaque bannissement +[recidive] +enabled = true +bantime = 1w +findtime = 1d +maxretry = 3 ``` +La jail `recidive` lit les bannissements de fail2ban lui-même : une adresse bannie trois +fois en vingt-quatre heures l'est ensuite pour une semaine. C'est ce qui sépare le robot +opportuniste, qui repasse toutes les heures, du visiteur qui s'est trompé de mot de passe. + `backend = systemd` est important sous Debian 13 : les journaux passent par `journald`, pas par un fichier `/var/log/auth.log` garanti. `banaction = nftables-multiport` évite un conflit avec le moteur nftables du système. -> Si vous avez choisi ufw à l'étape 10, utilisez plutôt `banaction = ufw`. +!!! note "Avec ufw plutôt que nftables" + Si vous avez choisi ufw à l'étape 10, utilisez plutôt `banaction = ufw`. ```bash sudo systemctl enable --now fail2ban @@ -761,10 +1121,13 @@ sudo fail2ban-client set sshd unbanip 198.51.100.7 # débannir sudo fail2ban-client set sshd banip 203.0.113.99 # bannir à la main ``` -> ⚠️ Mettez votre IP fixe dans `ignoreip` **avant** de démarrer le service. Sinon, une passphrase mal tapée quatre fois vous bannit une heure de votre propre serveur. +!!! warning "Votre IP dans `ignoreip`, avant de démarrer" + Mettez votre IP fixe dans `ignoreip` **avant** de démarrer le service. Sinon, une passphrase mal tapée quatre fois vous bannit une heure de votre propre serveur. ## Étape 12 — Mises à jour de sécurité automatiques +**Essentiel** — un serveur à jour aujourd'hui ne l'est plus dans trois semaines. + Un serveur non mis à jour devient vulnérable en quelques semaines. `unattended-upgrades` applique les correctifs de sécurité sans intervention. ```bash @@ -803,6 +1166,55 @@ Le choix structurant est `Automatic-Reboot` : Pour un serveur de production isolé, `"true"` avec une heure creuse est souvent le meilleur compromis. Pour un serveur critique sans redondance, gardez `"false"` et redémarrez lors d'une fenêtre planifiée. +### Le fichier qui décide vraiment + +`dpkg-reconfigure` écrit dans `/etc/apt/apt.conf.d/20auto-upgrades`, et c'est **ce +fichier** qui commande l'automatisation — pas `50unattended-upgrades`, qui n'en règle que +le détail. Vérifiez-le explicitement : + +```bash +cat /etc/apt/apt.conf.d/20auto-upgrades +``` + +Il doit contenir ces quatre directives : + +``` +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; +``` + +Sans la dernière à `"1"`, tout le reste est configuré pour rien : les index se +rafraîchissent, les paquets se téléchargent, et rien ne s'installe. + +### Rendre `needrestart` non interactif + +Installé à l'étape 5, `needrestart` pose une question quand des services doivent être +relancés. Sur un serveur, cette question bloque `unattended-upgrades` au lieu d'être +posée à quelqu'un : + +```bash +echo "\$nrconf{restart} = 'a';" | sudo tee /etc/needrestart/conf.d/50-auto.conf +``` + +`'a'` redémarre automatiquement les services concernés. Gardez `'l'` (lister seulement) +si vous préférez décider vous-même — mais alors n'activez pas le redémarrage automatique +d'`unattended-upgrades`, vous cumuleriez le pire des deux. + +### Savoir ce qui n'est plus suivi + +Tous les paquets d'une version stable ne sont pas couverts par l'équipe de sécurité +jusqu'au bout — certains sortent du périmètre en cours de vie. + +```bash +sudo apt install -y debian-security-support +check-support-status +``` + +À relancer après chaque gros `apt` : la commande liste ce qui est installé chez vous et +n'est plus, ou bientôt plus, suivi. + ### Vérifier que ça tourne ```bash @@ -822,8 +1234,42 @@ sudo needrestart -b # services à relancer Pensez à intégrer cette vérification à votre routine hebdomadaire. +### Contrôler, réparer, annuler + +**Résultat attendu** — les deux minuteries APT sont actives, et le journal montre des +installations réelles et non un simulacre : + +```bash +systemctl list-timers apt-daily.timer apt-daily-upgrade.timer +grep -c "Packages that will be upgraded" /var/log/unattended-upgrades/unattended-upgrades.log +``` + +**Si ça échoue** + +| Symptôme | Cause la plus fréquente | +| --- | --- | +| Le journal reste vide des jours durant | `APT::Periodic::Unattended-Upgrade` est à `"0"` dans `20auto-upgrades` | +| « No packages found that can be upgraded unattended » en permanence | `Origins-Pattern` ne couvre pas `trixie-security`, ou le dépôt de sécurité manque (étape 4) | +| Les mises à jour s'installent mais rien ne redémarre | `needrestart` en mode interactif : il attend une réponse que personne ne donnera | +| Le serveur redémarre à des heures imprévues | `Automatic-Reboot "true"` sans `Automatic-Reboot-Time` adapté | + +**Revenir en arrière** — pour suspendre l'automatisme sans désinstaller : + +```bash +sudo sed -i 's/^APT::Periodic::Unattended-Upgrade.*/APT::Periodic::Unattended-Upgrade "0";/' \ + /etc/apt/apt.conf.d/20auto-upgrades +sudo systemctl disable --now apt-daily-upgrade.timer +``` + +!!! warning "Suspendre, c'est accepter de faire à la main" + Un serveur sans mises à jour automatiques redevient vulnérable en quelques semaines. + Si vous coupez l'automatisme, inscrivez la mise à jour manuelle dans une routine + datée — pas dans vos bonnes intentions. + ## Étape 13 — Swap, /tmp et paramètres noyau +**Recommandé** — la stabilité au quotidien, et quelques durcissements noyau. + ### 13.1 Swap Beaucoup de VPS sont livrés sans swap. Un peu de swap évite qu'un pic de mémoire déclenche le tueur de processus du noyau (OOM killer). @@ -850,12 +1296,22 @@ Dimensionnement raisonnable : 2 Go jusqu'à 4 Go de RAM, puis 4 Go au-delà. Inu echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf ``` -> Sur SSD NVMe, le swap est peu coûteux. Sur un disque réseau facturé aux IOPS, il peut coûter cher et ralentir fortement. +!!! note "Le coût du swap dépend du stockage" + Sur SSD NVMe, le swap est peu coûteux. Sur un disque réseau facturé aux IOPS, il peut coûter cher et ralentir fortement. ### 13.2 /tmp est désormais en RAM Changement notable de Debian 13 : `/tmp` est monté en **tmpfs**, donc en mémoire, et vidé à chaque redémarrage ([note de version](https://www.debian.org/releases/trixie/release-notes/issues.en.html)). +!!! info "Pour comprendre : un système de fichiers en mémoire" + Un **tmpfs** ressemble à un répertoire ordinaire, mais son contenu n'est jamais écrit + sur le disque : il vit dans la RAM. D'où ses deux propriétés, qui expliquent tout le + reste — il est très rapide, et il disparaît intégralement à l'extinction. + + Cela veut aussi dire qu'un fichier de 3 Go déposé dans `/tmp` occupe 3 Go de mémoire + vive, au détriment des services. Sur un serveur à 2 Go de RAM, quelques fichiers + temporaires volumineux suffisent à provoquer un arrêt par manque de mémoire. + ```bash df -h /tmp findmnt /tmp @@ -920,15 +1376,50 @@ sudo sysctl --system sudo sysctl -a --pattern 'tcp_syncookies|rp_filter' # contrôle ``` -> N'appliquez pas `net.ipv4.ip_forward = 0` sans réfléchir : Docker, les conteneurs et les VPN en ont besoin. +!!! warning "Ne coupez pas le routage à l'aveugle" + N'appliquez pas `net.ipv4.ip_forward = 0` sans réfléchir : Docker, les conteneurs et + les VPN en ont besoin. + +### 13.4 AppArmor : vérifier qu'il travaille + +AppArmor est actif par défaut depuis Debian 12, et personne ne le regarde jamais. Il +confine chaque programme profilé à ce dont il a besoin : un nginx compromis ne peut pas +lire `/home`, même en tant que root, parce que le noyau le lui refuse. + +```bash +sudo apt install -y apparmor-utils +sudo aa-status +``` + +**Résultat attendu** — des profils chargés, et surtout des profils *en mode enforce* : + +``` +apparmor module is loaded. +XX profiles are loaded. +XX profiles are in enforce mode. +0 profiles are in complain mode. +``` + +Un profil en *complain* journalise les violations sans les bloquer : c'est le mode de mise +au point, pas un mode d'exploitation. Repassez-le en application avec +`sudo aa-enforce /etc/apparmor.d/`. + +Retenez surtout que les services installés plus tard apportent leurs profils : après avoir +déployé nginx ou MariaDB, un `aa-status` confirme qu'ils sont bien confinés. ## Étape 14 — Journaux, supervision et alertes +**Recommandé** — savoir ce qui se passe, et être prévenu quand plus rien ne se passe. + ### 14.1 Plafonner les journaux Par défaut, `journald` peut occuper jusqu'à 10 % du système de fichiers. Sur un petit VPS, c'est une cause classique de disque plein. +Le répertoire des drop-ins n'existe pas par défaut : créez-le **avant** d'ouvrir +l'éditeur, sinon l'enregistrement échoue. + ```bash +sudo mkdir -p /etc/systemd/journald.conf.d sudo nano /etc/systemd/journald.conf.d/00-limites.conf ``` @@ -941,7 +1432,6 @@ MaxRetentionSec=1month ``` ```bash -sudo mkdir -p /etc/systemd/journald.conf.d sudo systemctl restart systemd-journald journalctl --disk-usage ``` @@ -986,7 +1476,8 @@ sudo chmod 600 /etc/msmtprc echo "Test depuis web01" | mail -s "Test alerte" alix@exemple.fr ``` -> Le fichier contient un mot de passe en clair : `chmod 600` est obligatoire. Préférez un mot de passe d'application dédié, révocable, plutôt que votre mot de passe principal. +!!! warning "Un mot de passe en clair sur le disque" + Le fichier contient un mot de passe en clair : `chmod 600` est obligatoire. Préférez un mot de passe d'application dédié, révocable, plutôt que votre mot de passe principal. Redirigez ensuite le courrier de root vers vous : @@ -1026,8 +1517,50 @@ EOF sudo chmod +x /etc/cron.daily/alerte-disque ``` +### 14.5 Sortir les journaux de la machine + +Tout ce qui précède repose sur des journaux locaux. Or un attaquant qui obtient root +efface `/var/log` et l'historique de `journald` : il ne reste rien pour comprendre ce qui +s'est passé. Un journal n'a de valeur d'enquête que s'il existe **ailleurs**. + +Le principe : `journald` recopie vers syslog, et rsyslog expédie vers un collecteur +distant. + +```bash +# Sur la machine : autoriser le relais vers syslog +sudo mkdir -p /etc/systemd/journald.conf.d +echo -e "[Journal]\nForwardToSyslog=yes" \ + | sudo tee /etc/systemd/journald.conf.d/10-syslog.conf +sudo systemctl restart systemd-journald + +# Puis, dans rsyslog, une ligne de destination (TCP, port 514 par défaut) +# *.* @@collecteur.exemple.fr:514 +``` + +La mise en place complète — collecteur, chiffrement TLS, rétention — dépasse cette fiche. +Le minimum utile, si vous n'avez pas de collecteur : envoyer les alertes par courriel +(§14.3), dont la copie sort de la machine. + +### 14.6 Une sonde depuis l'extérieur + +L'alerte disque ci-dessus part **de la machine**. Le jour où celle-ci est éteinte, saturée +ou injoignable, elle n'envoie plus rien — et le silence ressemble à s'y méprendre au bon +fonctionnement. + +C'est la brique qui manque à presque tous les petits serveurs : une sonde externe, qui +teste depuis ailleurs et vous prévient quand la réponse ne vient pas. + +- **Uptime Kuma**, auto-hébergé sur une autre machine — idéalement chez un autre + hébergeur, sans quoi une panne de datacentre emporte la sonde avec le serveur ; +- ou un service de supervision gratuit, si vous n'avez qu'une machine. + +Surveillez au minimum le port SSH et, dès qu'ils existent, les ports 80 et 443 avec la +date d'expiration du certificat. + ## Étape 15 — Sauvegarde et instantané de référence +**Essentiel** — la seule étape qui rattrape l'échec de toutes les autres. + ### 15.1 Instantané immédiat Vous venez de construire une base propre. Prenez-en un instantané (*snapshot*) chez votre hébergeur **maintenant**, avant de déployer le moindre service. C'est votre point de retour : une mauvaise manipulation plus tard se résoudra en trois minutes au lieu d'une réinstallation complète. @@ -1048,7 +1581,8 @@ Récupérez l'archive sur votre poste : scp -P 2222 alix@web01.exemple.fr:/root/config-initiale-*.tar.gz ./ ``` -> Mieux encore : versionnez ces fichiers dans un dépôt Git privé — sans les secrets. `git` est déjà installé à l'étape 5. +!!! tip "Mieux : versionner la configuration" + Mieux encore : versionnez ces fichiers dans un dépôt Git privé — sans les secrets. `git` est déjà installé à l'étape 5. ### 15.3 Mettre en place une vraie sauvegarde @@ -1058,25 +1592,90 @@ Une sauvegarde qu'on n'a jamais restaurée n'est pas une sauvegarde. Principe de sudo apt install -y restic ``` +Les paramètres de restic se passent par variables d'environnement. Comme `sudo` les +efface (`env_reset` est le défaut sous Debian), déposez-les dans un fichier lu par root +plutôt que de compter sur un `export` dans votre session : + ```bash -# Initialisation vers un stockage distant (une seule fois) -export RESTIC_REPOSITORY="sftp:sauvegarde@stockage.exemple.fr:/backups/web01" -export RESTIC_PASSWORD_FILE="/root/.restic-pass" -sudo restic init - -# Sauvegarde -sudo restic backup /etc /home /var/www --exclude-caches - -# Vérification et purge -sudo restic snapshots -sudo restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune +sudo install -m 600 /dev/null /root/.restic-env +sudo tee /root/.restic-env >/dev/null <<'EOF' +RESTIC_REPOSITORY=sftp:sauvegarde@stockage.exemple.fr:/backups/web01 +RESTIC_PASSWORD_FILE=/root/.restic-pass +EOF +sudo install -m 600 /dev/null /root/.restic-pass +sudo nano /root/.restic-pass # la phrase de passe du dépôt, seule sur sa ligne ``` -Automatisez avec une minuterie systemd plutôt qu'un cron : les échecs remontent dans `journalctl`. +!!! danger "Cette phrase de passe n'est récupérable nulle part" + Un dépôt restic dont on a perdu la phrase de passe est définitivement illisible. + Recopiez-la hors du serveur — gestionnaire de mots de passe ou coffre de + l'association — avant d'aller plus loin. + +Initialisation et premières commandes, en chargeant explicitement ce fichier : ```bash -sudo systemd-run --on-calendar="*-*-* 03:00:00" --unit=sauvegarde \ - /usr/bin/restic backup /etc /home /var/www +# Initialisation du dépôt distant (une seule fois) +sudo env $(cat /root/.restic-env | xargs) restic init + +# Sauvegarde +sudo env $(cat /root/.restic-env | xargs) restic backup /etc /home /var/www --exclude-caches + +# Vérification et purge +sudo env $(cat /root/.restic-env | xargs) restic snapshots +sudo env $(cat /root/.restic-env | xargs) restic forget \ + --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune +``` + +Automatisez avec une minuterie systemd plutôt qu'un cron : les échecs remontent dans +`journalctl`. + +!!! warning "N'utilisez pas `systemd-run` pour une sauvegarde" + `systemd-run --on-calendar` crée une unité **transitoire** : elle disparaît au + redémarrage, et la sauvegarde cesse silencieusement. On ne s'en aperçoit que le jour + où l'on en a besoin. Écrivez de vraies unités. + +```bash +sudo tee /etc/systemd/system/sauvegarde.service >/dev/null <<'EOF' +[Unit] +Description=Sauvegarde restic +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +EnvironmentFile=/root/.restic-env +ExecStart=/usr/bin/restic backup /etc /home /var/www --exclude-caches +ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune +EOF + +sudo tee /etc/systemd/system/sauvegarde.timer >/dev/null <<'EOF' +[Unit] +Description=Sauvegarde restic quotidienne + +[Timer] +OnCalendar=*-*-* 03:00:00 +RandomizedDelaySec=30m +Persistent=true + +[Install] +WantedBy=timers.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now sauvegarde.timer +``` + +`Persistent=true` rattrape l'exécution manquée si la machine était éteinte à 3 h. +`EnvironmentFile` règle le problème des variables : le service les lit lui-même, sans +dépendre de votre session. + +**Résultat attendu** — la minuterie apparaît avec sa prochaine échéance, et la première +exécution se déclenche à la demande : + +```bash +systemctl list-timers sauvegarde.timer +sudo systemctl start sauvegarde.service +journalctl -u sauvegarde.service -n 20 ``` ### 15.4 Tester la restauration @@ -1090,6 +1689,33 @@ cat /tmp/test-restauration/etc/hosts Notez la date du dernier test réussi quelque part de visible. C'est la seule métrique qui compte vraiment. +## Étape 16 — Auditer ce qui a été fait + +**Recommandé** — vérifier que ce qui est écrit ici a réellement pris. + +Vous avez appliqué quinze étapes ; un outil d'audit vous dira lesquelles ont réellement +pris. **Lynis** parcourt la machine et note ce qu'il trouve. + +```bash +sudo apt install -y lynis +sudo lynis audit system +``` + +Le rapport se termine par un **index de durcissement** sur 100, et surtout par une liste +de suggestions numérotées, chacune avec sa justification. + +**Résultat attendu** — sur une Debian fraîchement installée, l'index tourne autour de +**55 à 60** ; après un parcours comme celui-ci, il monte typiquement vers **70 à 75**. + +!!! warning "Ne courez pas après le 100" + Les points restants dépendent de ce que la machine fait : durcir un noyau au-delà du + raisonnable, ou désactiver des modules dont vos services auront besoin, se paie en + pannes incompréhensibles six mois plus tard. Lisez les suggestions, appliquez celles + qui ont un sens pour votre usage, et notez pourquoi vous écartez les autres. + +Le rapport complet reste dans `/var/log/lynis-report.dat`. Relancez l'audit après chaque +déploiement de service : c'est la meilleure façon de voir ce qu'un `apt install` a ouvert. + ## Checklist récapitulative ### Préparatifs @@ -1102,6 +1728,9 @@ Notez la date du dernier test réussi quelque part de visible. C'est la seule m ### Identité et système - [ ] Première connexion réussie, état initial relevé (`ss -tulpn`) +- [ ] **Empreinte de la clé d'hôte comparée** avec celle publiée par l'hébergeur +- [ ] Clés d'hôte régénérées si l'image était clonée, `machine-id` vérifié +- [ ] Partition étendue à tout le volume facturé (`lsblk`, `growpart`) - [ ] Nom d'hôte court défini avec `hostnamectl set-hostname` - [ ] `/etc/hosts` corrigé : FQDN avant nom court sur la ligne `127.0.1.1` - [ ] `hostname -f` renvoie bien le FQDN @@ -1117,7 +1746,8 @@ Notez la date du dernier test réussi quelque part de visible. C'est la seule m - [ ] Compte administrateur créé et ajouté au groupe `sudo` (avec `-aG`) - [ ] `sudo -v` et `sudo whoami` fonctionnent depuis le nouveau compte - [ ] Clé Ed25519 générée et déposée, connexion par clé vérifiée -- [ ] Drop-in `sshd_config.d` créé : root refusé, mot de passe désactivé, `AllowUsers` posé +- [ ] Drop-in `sshd_config.d` créé : root refusé, mot de passe désactivé, `AllowGroups` posé +- [ ] Groupe `ssh-users` créé et peuplé — **avec un second administrateur** - [ ] `sudo sshd -T` confirme la configuration réellement appliquée - [ ] Port SSH vérifié côté `ssh.socket` si vous l'avez changé - [ ] Compte fournisseur verrouillé, ses clés SSH retirées @@ -1130,6 +1760,10 @@ Notez la date du dernier test réussi quelque part de visible. C'est la seule m - [ ] fail2ban actif, votre IP fixe dans `ignoreip` - [ ] `unattended-upgrades` actif, politique de redémarrage décidée - [ ] Paramètres sysctl appliqués (`sysctl --system`) +- [ ] `20auto-upgrades` contrôlé : `Unattended-Upgrade "1"` +- [ ] `needrestart` passé en mode non interactif +- [ ] `check-support-status` consulté (paquets hors support sécurité) +- [ ] `aa-status` : profils AppArmor chargés et en mode *enforce* - [ ] Swap présent si la RAM est limitée ### Exploitation @@ -1139,6 +1773,9 @@ Notez la date du dernier test réussi quelque part de visible. C'est la seule m - [ ] Sauvegarde configurée **et restauration testée une fois** - [ ] Instantané de référence pris chez l'hébergeur - [ ] Archive de configuration récupérée hors du serveur +- [ ] Minuterie de sauvegarde **persistante** (`.timer` + `Persistent=true`), pas un `systemd-run` +- [ ] Sonde externe en place, sur une autre machine que celle-ci +- [ ] `lynis audit system` passé, suggestions lues et arbitrées - [ ] TTL DNS remonté à sa valeur normale ### Vérification finale d'un seul bloc @@ -1147,13 +1784,110 @@ Notez la date du dernier test réussi quelque part de visible. C'est la seule m hostname -f; echo '---' localectl status | head -3; echo '---' timedatectl | grep -E 'Time zone|synchronized'; echo '---' -sudo sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|allowusers)'; echo '---' +sudo sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|allowgroups)'; echo '---' sudo ufw status verbose 2>/dev/null || sudo nft list ruleset | head -20; echo '---' sudo fail2ban-client status; echo '---' systemctl --failed; echo '---' free -h; df -h / ``` +## Si vous êtes verrouillé dehors + +Cela arrive, malgré la session de secours — elle finit par expirer, ou l'on referme la +mauvaise fenêtre. Voici l'ordre dans lequel chercher. + +**1. Est-ce vraiment le serveur ?** Depuis un autre réseau (partage de connexion du +téléphone), testez le port : + +```bash +nc -vz 203.0.113.10 2222 +``` + +Si la connexion passe d'ailleurs mais pas de chez vous, c'est fail2ban ou le pare-feu de +l'hébergeur qui a banni votre adresse, pas la configuration du serveur. + +**2. La console de l'hébergeur.** KVM, VNC ou console série : elle donne un accès clavier +indépendant de SSH et du réseau. C'est la voie la plus rapide, et la raison pour laquelle +cette fiche demande de vérifier son existence **avant** de commencer. + +Une fois connecté, retirez ce qui bloque : + +```bash +sudo rm /etc/ssh/sshd_config.d/99-durcissement.conf +sudo systemctl restart ssh +sudo ufw disable # ou : sudo nft flush ruleset +sudo fail2ban-client unban --all +``` + +**3. Le mode rescue.** Si même la console ne répond plus, l'hébergeur propose un démarrage +sur un système de secours. Le disque de la machine s'y monte, et l'on répare de +l'extérieur : + +```bash +lsblk # identifier la partition racine +sudo mount /dev/vda1 /mnt +sudo mount --bind /dev /mnt/dev +sudo mount --bind /proc /mnt/proc +sudo mount --bind /sys /mnt/sys +sudo chroot /mnt /bin/bash + +# Dans le chroot : annuler la modification fautive +rm /etc/ssh/sshd_config.d/99-durcissement.conf +sed -i 's/^Port .*/Port 22/' /etc/ssh/sshd_config +systemctl disable nftables + +exit +sudo umount -R /mnt +``` + +Redémarrez sur le disque, reconnectez-vous, et reprenez la configuration en gardant cette +fois deux sessions ouvertes. + +!!! tip "Ce qui verrouille, dans l'ordre de fréquence" + Un port SSH changé sans ouvrir le nouveau dans le pare-feu ; un `AllowGroups` posé + avant que le groupe n'existe ; `PasswordAuthentication no` avec une clé qui n'a jamais + été testée ; une politique `drop` activée sans règle SSH au-dessus. Les quatre se + préviennent en testant depuis une **seconde** session avant de fermer la première. + +## Et après ? + +Le serveur est prêt à recevoir ses services — c'est là que cette fiche s'arrête et que les +autres commencent : + +- [Docker](../guides/docker.md) — installer le moteur de conteneurs sur cette base ; +- [Nextcloud](nextcloud.md) et [Matrix](matrix.md) — deux services que l'association + héberge ; +- [Déploiement du wiki](deploiement-wiki.md) — l'exemple d'un service statique, webhook + compris. + +Ouvrez les ports au moment du déploiement, pas avant, et repassez un `lynis audit system` +derrière. + +!!! tip "Le deuxième serveur ne se prépare pas à la main" + Cette checklist a exactement la forme d'un playbook. Le jour où vous installez une + deuxième machine, transposez-la dans **Ansible** : chaque étape devient une tâche + idempotente, l'ordre est écrit noir sur blanc, et la configuration cesse de vivre dans + la mémoire de celui qui l'a faite. Des rôles publics existent pour Debian 13 — + lisez-les avant de les appliquer, ils durcissent parfois plus que vous ne le souhaitez. + +## Ce que vous avez construit + +En cinq lignes, pour consolider ce que la checklist vérifie point par point : + +1. **Une identité cohérente** — un FQDN qui se résout dans les deux sens, un nom d'hôte + stable, une heure juste. Sans cela, les journaux mentent et les mails partent en + indésirables. +2. **Un accès nominatif et robuste** — une clé par personne, un groupe qui décide qui + entre, le compte du fournisseur neutralisé, root inaccessible depuis le réseau. +3. **Une surface réduite et surveillée** — un pare-feu qui ferme par défaut, fail2ban qui + écarte les robots, AppArmor qui confine les services, des journaux plafonnés et lisibles. +4. **Une machine qui se maintient seule** — les correctifs de sécurité s'installent, ce qui + n'est plus suivi se signale, et vous êtes prévenu de ce qui demande un redémarrage. +5. **Une porte de sortie** — un instantané de référence, une sauvegarde dont la + restauration a été testée, et une sonde extérieure pour vous dire que la machine vit. + +Le reste — les services, leurs ports, leurs bases de données — se construit par-dessus. + ## Ce qui change par rapport à l'ancienne fiche Debian 11 L'ancienne fiche couvrait l'essentiel de la mise en route. Voici ce qui y était inexact ou @@ -1166,7 +1900,7 @@ incomplet, et pourquoi c'est corrigé ici. | `adduser nom` | `sudo adduser nom` | La création d'utilisateur demande les privilèges root | | Droits supplémentaires via `visudo` | `usermod -aG sudo` d'abord ; `visudo -f /etc/sudoers.d/10-nom` si besoin spécifique | L'appartenance au groupe suffit dans 95 % des cas. Un fichier dédié isole une erreur de syntaxe au lieu de casser `sudo` pour tout le monde | | `apt upgrade -y` | `apt full-upgrade -y` | `upgrade` refuse d'installer ou de retirer des paquets, donc bloque sur des transitions courantes d'une machine neuve | -| « Désactiver ssh pour l'utilisateur `debian` » | `AllowUsers` en liste blanche + verrouillage du compte | Une liste blanche couvre aussi les comptes non anticipés ; le verrouillage ferme les autres voies que SSH | +| « Désactiver ssh pour l'utilisateur `debian` » | `AllowGroups ssh-users` + verrouillage du compte | Une liste blanche couvre aussi les comptes non anticipés ; un groupe survit à l'arrivée d'un second administrateur ; le verrouillage ferme les autres voies que SSH | ### Ce que l'ancienne fiche ne couvrait pas