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