La menace qu'on vous a confiée, pas celle du guide
Une machine neuve reçoit une adresse publique et, quelques minutes plus tard, on frappe déjà à la porte. Le trafic est automatisé, non ciblé et permanent : des dictionnaires de noms d'utilisateurs, des mots de passe issus de vieilles fuites, un balayage de tous les ports dans l'espoir que quelque chose réponde. C'est la météo de l'Internet public, pas une attaque contre vous.
La plupart des guides de durcissement sont écrits comme si l'adversaire du lecteur l'avait choisi. Cet adversaire existe — et presque rien de la liste habituelle ne le retarde plus de quelques secondes. Le modèle de menace réaliste des premiers jours tient en trois entrées, qui ne sont pas équiprobables :
- La devinette d'identifiants non ciblée. Supprimée entièrement en supprimant les mots de passe, pas en cachant le service.
- Une porte laissée par celui qui a touché la machine avant vous. Une entrée oubliée dans un fichier
authorized_keys, un compte cloud-init par défaut, un paquet qui a ouvert un port le jour de son installation. - Vous. Vous enfermer dehors, ou écrire une configuration qui tient jusqu'au prochain redémarrage.
Classées par probabilité de gâcher votre semaine, la troisième arrive en tête. D'où la thèse de cet article : durcir SSH consiste surtout à supprimer une classe entière d'identifiants sans casser son propre accès, et les points sur lesquels tout le monde débat — le numéro de port, le réglage de fail2ban — sont ceux qui comptent le moins. On les fera quand même, parce qu'ils achètent quelque chose de réel. Simplement pas ce que l'on croit.
Une limite avant de commencer : on parle ici de la porte d'administration. Rien de ce qui suit ne dit quoi que ce soit de l'application que vous allez faire tourner derrière.
Avant la première modification : se réserver une porte de sortie
Toutes les étapes qui suivent sont réversibles, sauf celle qui coupe votre session. On commence donc par là.
- Garder la session en cours ouverte pendant toute l'opération. Un
reloadne coupe pas les connexions établies, unrestartnon plus — mais une configuration qui ne se parse pas ne laisse plus rien en écoute pour la suivante. - Tester son accès hors bande maintenant, pendant que rien n'est cassé. La console de l'hébergeur ou le KVM, c'est ce qui vous sauve, et cela réclame très souvent un mot de passe local que vous n'avez jamais défini, ou un mode rescue que vous n'avez jamais démarré. Le découvrir au moment où on en a besoin, c'est exactement la panne.
sshd -tvalide la syntaxe.sshd -Taffiche la configuration effective, une fois résolus tous lesIncludeet tous les blocsMatch. C'est la seconde qui attrape l'erreur, et cet article y revient sans arrêt.
Écrivez vos directives dans un fichier dédié — /etc/ssh/sshd_config.d/10-durcissement.conf — plutôt que dans le fichier livré par la distribution, pour qu'une mise à jour ne vous revienne pas dessus en silence. Deux détails mécaniques décident du résultat : la ligne Include doit exister, et elle se trouve en général en tête du fichier du paquet ; et pour la plupart des mots-clés, sshd retient la première valeur lue, pas la dernière. Un fichier qui se classe après celui de la distribution perd, sans rien dire. Le préfixe numérique n'est pas décoratif.
L'unité s'appelle ssh sur Debian et Ubuntu, sshd dans la famille RHEL. Se tromper là-dessus dans un runbook, c'est un runbook qui échoue sur la moitié du parc.
1. Supprimer le mot de passe, pas le port
C'est la seule mesure qui change la nature du problème plutôt que son volume. Un mot de passe se devine depuis n'importe où, par n'importe qui, indéfiniment, pour un coût nul. Une clé, non. Une fois les mots de passe partis, la première catégorie du modèle de menace part avec eux : pas réduite, supprimée.
L'ordre compte :
- générer une clé ed25519 (
ssh-keygen -t ed25519) et lui donner une phrase de passe — une clé sans phrase de passe est un fichier de mot de passe mieux emballé ; - la déposer (
ssh-copy-id) et ouvrir une seconde session avec elle avant de modifier quoi que ce soit ; - alors, et seulement alors, poser
PasswordAuthentication noetKbdInteractiveAuthentication no.
La seconde directive n'est pas redondante. Le clavier interactif est un chemin d'authentification distinct, que PAM peut encore satisfaire avec un mot de passe sur certaines images — c'est la raison pour laquelle tant de serveurs « sans authentification par mot de passe » en acceptent toujours une. Ne faites pas confiance au fichier, interrogez le démon :
sshd -T | grep -Ei 'passwordauth|kbdinteractive|pubkeyauth|permitrootlogin'
Cette ligne vaut plus que tout le fichier de configuration, parce qu'elle rapporte ce que sshd a résolu et non ce que vous croyez avoir écrit.
Tant qu'on y est : MaxAuthTries ne compte pas ce qu'on croit. Chaque clé publique proposée par le client compte comme une tentative. Un poste qui garde six clés dans son agent peut être déconnecté avant d'avoir proposé la bonne, ce qui, vu de l'extérieur, ressemble trait pour trait à un verrouillage. Soit vous laissez une valeur généreuse, soit vous cadrez le client avec IdentitiesOnly yes et un IdentityFile explicite dans votre ~/.ssh/config.
2. Root : la question est le rayon d'explosion, pas la solidité
PermitRootLogin prohibit-password conserve l'accès root par clé et refuse le mot de passe. PermitRootLogin no vous oblige à arriver sous un compte nommé puis à élever vos droits. Le gain du second sur le premier n'est pas cryptographique — une clé root vaut exactement une autre clé. Le gain est l'imputabilité : chaque action privilégiée passe par un compte nommé et laisse une ligne sudo dans le journal, ce qui fait toute la différence entre savoir que root a fait quelque chose et savoir qui l'a fait.
Le corollaire que personne n'écrit : sur une machine sans second compte d'administration et sans console réellement testée, PermitRootLogin no n'est pas du durcissement. C'est un verrouillage qui attend une mauvaise clé.
Et en dessous se cache la décision que l'on prend par inadvertance — le NOPASSWD dans sudoers. Une fois l'authentification par mot de passe désactivée, votre clé privée est le seul facteur entre Internet et la machine. Si sudo ne demande rien, une clé volée donne root immédiatement. Si sudo demande un mot de passe, ce mot de passe est un second facteur qui vit dans votre tête et ne circule jamais. Le coût, c'est l'automatisation sans opérateur qui casse — d'où le NOPASSWD posé un jour et oublié le lendemain. Choisissez délibérément, machine par machine, et écrivez le choix à côté de la machine.
Enfin, restreignez qui a seulement le droit de tenter : AllowGroups ssh-users vaut mieux que AllowUsers, parce qu'il survit à l'arrivée de la personne suivante, et parce qu'il survit au jour où un paquet crée discrètement un compte de service avec un shell.
3. Changer le port relève du décor — et le gain est réel quand même
Disons-le franchement : changer le port ne supprime rien. Un scan complet retrouve le service en quelques secondes, et celui qui vous a choisi pour cible n'est pas ralenti. Si votre posture repose sur le caractère inhabituel du 2222, vous n'avez pas de posture.
Ce que ça achète réellement, c'est un journal lisible. Sur le port 22, un VPS ordinaire accumule des milliers de lignes Invalid user admin par jour, et vos vrais événements — une connexion réussie à trois heures du matin, un bannissement qui a attrapé l'adresse de votre bureau — s'y noient. Déplacez le port et le journal redevient un document qu'un humain peut lire, ce qui est la condition préalable pour y repérer une anomalie. C'est un gain d'exploitation. Il vaut la peine d'être pris, tant qu'on ne le comptabilise pas comme une mesure de sécurité.
Trois pièges mécaniques, dans l'ordre où ils mordent :
- L'activation par socket. Sur les versions récentes d'Ubuntu, sshd est démarré par
ssh.socket, et la directivePortdesshd_configest alors purement ignorée : la socket en écoute appartient à l'unité. Vous changez le port, rien ne se passe, vous redémarrez, et là vous découvrez. La correction passe parsystemctl edit ssh.socket: unListenStream=vide pour effacer la valeur héritée, puis celle que vous voulez. - SELinux. Dans la famille RHEL, le démon refuse de se lier à un port non étiqueté tant que vous n'avez pas passé
semanage port -a -t ssh_port_t -p tcp 2222. - La plage privilégiée. En dessous de 1024, seul root peut se lier. Au dessus, si sshd est un jour arrêté, n'importe quel processus local peut prendre le port. Marginal sur une machine à un seul administrateur, beaucoup moins sur une machine à utilisateurs locaux non fiables.
4. fail2ban, et ce qu'il fait vraiment
Le mécanisme mérite d'être énoncé, parce qu'il explique les limites. fail2ban lit le journal, reconnaît des motifs d'échec avec une expression régulière, et insère une règle de pare-feu pour la durée du bantime. C'est un lecteur de journal avec un pare-feu attaché. Rien de plus.
Rapprochez ça de la section précédente. Une fois l'authentification par mot de passe retirée, il ne reste presque plus rien à échouer : le robot n'atteint jamais d'invite, et le journal se remplit de Connection closed by authenticating user root … [preauth]. fail2ban bannira ces adresses avec entrain, et il faut le laisser faire — mais soyons honnêtes sur l'achat. Vous avez acheté moins de volume de journal et moins de poignées de main TCP. Vous n'avez pas acheté une défense d'identifiants : vous en aviez déjà une.
Les limites, sans détour :
- un botnet distribué qui fait une tentative par adresse n'atteint jamais le
maxretry. fail2ban est conçu contre la répétition, et la répétition est ce qu'un attaquant abandonne au moindre coût ; - les adresses sont bon marché, et en IPv6 quasiment gratuites : bannir un
/128quand le client dispose d'un préfixe entier est une erreur d'arrondi. Bannir le préfixe fonctionne, et emporte des dommages collatéraux ; - il vous bannira. Mettez vos propres plages dans
ignoreip, en sachant que ça ne vous sauvera pas le jour où vous vous connecterez depuis un hôtel ; - il ne voit pas à travers un intermédiaire. Derrière un proxy ou un NAT, toutes les tentatives arrivent de la même adresse : le bannissement ne fait rien, ou frappe tout le monde d'un coup ;
bantime.incrementest le seul réglage qui change vraiment l'économie de l'attaquant : les récidivistes prennent des bannissements de plus en plus longs au lieu d'un forfait de dix minutes.
Les services où fail2ban gagne réellement sa place sont ceux où les mots de passe doivent continuer d'exister : un formulaire de connexion, un relais de messagerie, un point d'accès qu'on n'a pas encore réussi à retirer. Sur un port SSH sans mot de passe, c'est de l'hygiène, pas une défense.
Pendant ce temps, sshd dispose de ses propres limiteurs, qui ne coûtent rien et s'appliquent avant qu'une seule ligne de journal soit écrite : MaxStartups (dans sa forme 10:30:60) plafonne le nombre de connexions non authentifiées simultanées, et LoginGraceTime le temps que chacune peut y rester. Sous-utilisés, et ils continuent de fonctionner quand votre lecteur de journal est à l'arrêt.
5. Le pare-feu : refus par défaut, et l'ordre qui vous enferme dehors
Ce qui compte, c'est la politique par défaut, pas les exceptions : refuser tout le trafic entrant, puis autoriser la poignée de ports que vous savez nommer et justifier. Tout ce qu'un paquet a ouvert le jour de son installation disparaît, ce qui traite la deuxième entrée du modèle de menace.
L'ordre n'est pas négociable. On autorise SSH d'abord, on active le pare-feu ensuite. Inverser ces deux commandes est la panne auto-infligée la plus fréquente de toute cette liste, et le moment où la console testée plus haut cesse d'être théorique.
Mieux que le changement de port, quand la situation le permet : restreindre la règle SSH à une plage d'adresses source. Une liste d'autorisation est une vraie mesure, là où un numéro de port inhabituel n'est qu'un journal plus calme.
Ce qu'un pare-feu ne fait pas, dit à voix haute :
- il ne protège pas ce que vous exposez volontairement. Un filtre de paquets devant une application web vulnérable ne change rien à l'application web ;
- le trafic sortant reste ouvert tant que vous n'en décidez pas autrement, et le filtrer est un vrai durcissement au vrai coût d'exploitation — chaque agent, chaque sauvegarde, chaque miroir de paquets réclame alors une règle explicite ;
- sur une instance chez un hébergeur, il existe un second pare-feu du côté du fournisseur, et c'est celui-là que vous oublierez. Vérifiez les deux, sinon vous passerez un après-midi à déboguer une règle qui n'a jamais été le problème.
6. Les mises à jour automatiques : le meilleur rapport de la liste
Si vous ne faites qu'une chose de plus après avoir supprimé les mots de passe, faites celle-ci. Les petits serveurs se perdent rarement sur une attaque astucieuse. Ils se perdent sur une vulnérabilité connue dans quelque chose d'exposé, publiée des semaines plus tôt et corrigée en amont bien avant que quiconque n'approche la machine. Automatiser les mises à jour de sécurité n'a aucun équivalent en rapport effort/bénéfice.
Activez unattended-upgrades, limitez les origines autorisées au dépôt de sécurité plutôt qu'à toutes les mises à jour, prévoyez un redémarrage automatique dans une fenêtre explicite, et lancez-le une fois en --dry-run --debug pour lire ce qu'il compte faire avant qu'il ne le fasse à quatre heures du matin.
Puis acceptez les trois coûts, parce qu'ils sont réels :
- un service relancé en plein après-midi par
needrestartest une petite interruption. Décidez de la fenêtre ; ne la découvrez pas ; - un redémarrage automatique suppose que votre application revienne toute seule. Le risque que vous prenez n'est pas la mise à jour, c'est un ordre de démarrage jamais testé — vérifiez que tout ce dont vous avez besoin est
enabled, et pas simplementactive; - cela couvre ce que le gestionnaire de paquets a installé, et rien d'autre. Un binaire déposé dans
/usr/local/bin, une image de conteneur figée, un runtime installé par un gestionnaire de versions : ceux-là restent à votre charge, et c'est exactement là que se logera la vulnérabilité oubliée.
Ce que tout cela ne protège pas
Un article de durcissement qui se termine sur un tour d'honneur ment par omission. Ce que vous venez de faire rétrécit une porte. Cela ne dit rien d'une vulnérabilité dans l'application que vous exposez, rien d'un portable volé avec une clé privée non chiffrée dessus, rien d'une dépendance compromise dans ce que vous livrez, rien de quelqu'un disposant d'un accès légitime qui commet une erreur d'apparence légitime, et rien de la perte de données — le durcissement n'est pas une sauvegarde, et on confond les deux régulièrement.
Aucune configuration ne rend un serveur invulnérable. Celle-ci supprime entièrement les attaques bon marché et oblige les attaques coûteuses à laisser des traces. C'est tout le gain disponible, et il vaut la peine d'être pris.
La vérification qui prouve que vous n'êtes pas enfermé dehors
Depuis un second terminal, le premier toujours ouvert, dans cet ordre :
- se connecter avec
ssh -vsur le nouveau port, sous l'utilisateur prévu, et lire la méthode d'authentification dans la sortie plutôt que de la supposer ; - lancer
sudo -l, et si vous avez configuré un mot de passe sudo, s'en servir une fois pour de vrai ; sshd -T, filtré sur les directives que vous avez posées : port, politique root, les deux interrupteurs d'authentification,allowgroups,maxauthtries;ss -tulpn— qui écoute, sur quelle adresse, sous quel processus. C'est là qu'on trouve ce qu'on ne savait pas exposer ;- la liste des règles de pare-feu, sur la machine et chez l'hébergeur ;
systemctl is-enabledsur l'unité ou la socket ssh, sur fail2ban et sur les mises à jour automatiques —is-activene parle que de l'instant présent ;- redémarrer. Tout ce qui précède prouve l'état courant ; seul un redémarrage prouve l'état persistant, et une configuration qui tient jusqu'à la prochaine mise à jour de noyau n'est pas du durcissement. Se reconnecter, puis lire
journalctl -u ssh -bpour voir ce qui a réellement démarré.
Ensuite, arrangez-vous pour apprendre qu'une porte est cassée par une machine, et non par votre propre tentative suivante. Une vérification SSH sur le port, à intervalle lent, avec assez d'hystérésis pour qu'un paquet perdu ne réveille personne, fait la différence entre l'apprendre demain matin et l'apprendre dans trois semaines, au moment précis où vous avez besoin d'entrer. Régler cela sans créer une nouvelle source de bruit fait l'objet d'un article à part sur le bruit d'alerte.
Ce que Servor apporte là-dedans
Servor livre un runbook de durcissement SSH parmi ses 27 officiels — fail2ban, UFW et les mises à jour automatiques y sont aussi. Le runbook est la bonne forme pour ce travail : une séquence relue, avec une vérification explicite à la fin, rejouable sur la machine suivante au lieu d'être retapée de mémoire. L'exécution passe par l'agent, qui ouvre une connexion sortante vers le plan de contrôle : resserrer ou restreindre SSH ne coupe donc pas votre chemin d'administration — le SSH ne sert qu'à l'amorçage, le temps d'installer l'agent. Chaque commande rejoint un historique unique avec sa sortie, son code de retour et sa durée, et le journal d'audit chaîné par empreintes répond encore, six mois plus tard, à « qui a modifié sshd_config, quand, et qu'est-ce que ça a renvoyé ».
Deux choses à savoir avant de le pointer sur une machine. La liste de commandes interdites refuse l'écriture directe dans /etc/passwd, /etc/shadow et /etc/sudoers, côté plan de contrôle et de nouveau côté agent — c'est délibéré, et cela veut dire que la décision sudoers reste manuelle. Et si vous préférez qu'une machine soit seulement observée, le mode strict l'installe en supervision seule, le plan de contrôle refusant alors toute exécution.
Pour la partie exploitation, voir notre guide de l'exploitation de serveurs ; pour la manière dont une commande est approuvée, signée puis vérifiée, l'article Plan-Execute-Verify.