Ajoute l'authentification par clé SSH (machine cliente et serveur) - #3
Conversation
Le script sécurisait SSH (port, PermitRootLogin) mais laissait le mot de passe comme seul moyen de connexion. Une nouvelle étape 8 met en place l'authentification par clé, dans un sens ou dans l'autre selon le rôle de la machine. Machine cliente — génération d'une paire de clés : - compte propriétaire, type (ed25519, rsa 4096, ecdsa 521, ed25519-sk), emplacement, nom de fichier et commentaire au choix, tous validés ; - la phrase de passe est demandée par ssh-keygen lui-même et jamais passée via -N : la ligne de commande d'un processus est lisible de tous par ps ; - génération sous l'identité du compte via runuser, avec repli en root suivi d'un chown ; repli proposé sur ed25519 si aucun jeton FIDO2 ne répond ; - aucune clé existante n'est écrasée en silence : autre nom, ou sauvegarde préalable ; - options : bloc idempotent dans ~/.ssh/config, envoi par ssh-copy-id puis test de connexion réel. Serveur — dépôt d'une clé publique : - provenance au choix : collage, fichier, URL https, compte GitHub, clé générée à l'instant, ou aide à la création côté poste client ; - validation du type (les clés DSA sont écartées), contrôle croisé entre le type annoncé et l'entête base64 du blob, refus d'une clé privée collée par erreur, retraits des retours chariot Windows ; - installation idempotente (comparaison sur le corps de la clé, pas sur la ligne), saut de ligne final garanti, droits 700/600 et propriétaire corrigés ; - détection de StrictModes, qui fait ignorer la clé sans aucun message, et vérification de l'AuthorizedKeysFile réellement en vigueur ; - test de connexion en boucle locale, seule preuve possible depuis le serveur. Durcissement et garde-fou anti-lockout : - PubkeyAuthentication yes systématique ; la coupure du mot de passe (PasswordAuthentication et KbdInteractiveAuthentication) n'est proposée qu'après une connexion prouvée ou une confirmation explicite, et le résultat est vérifié par sshd -T ; - si un fichier de sshd_config.d numéroté avant le nôtre l'emporte, il est nommé au lieu d'annoncer un succès qui n'a pas eu lieu ; - sur le modèle de ip-fixe-confirmer, ssh-cles-rollback et ssh-cles-confirmer sont installés et une minuterie systemd réactive le mot de passe faute de confirmation. Ajoute aussi backup_file_once, pour qu'une seconde sauvegarde du même fichier sshd n'écrase pas la copie d'origine par la version modifiée. Tests : 30 nouveaux cas sur les fonctions pures (138 au total), fondés sur de vraies clés publiques. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbhHGjLvttKAauNJjPJMuq
There was a problem hiding this comment.
Pull request overview
Ce PR étend le script de personnalisation Debian 13 en ajoutant une 8ᵉ étape dédiée à l’authentification SSH par clé, couvrant à la fois le rôle client (génération de paire de clés) et le rôle serveur (dépôt de clé publique dans authorized_keys, puis durcissement optionnel avec garde-fou anti-lockout). Il s’inscrit dans la continuité de l’étape 7 (durcissement SSH) et complète la sécurisation en réduisant la dépendance au mot de passe.
Changes:
- Ajout d’une section complète “Authentification par clé SSH” (validateurs, collecte/installation idempotente de clés, test de connexion, rollback via systemd-run).
- Ajout de tests unitaires Bash pour les nouvelles fonctions “pures” liées aux clés SSH et à la validation.
- Mise à jour de la documentation (README) pour décrire l’étape 8 et ses options.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
script-de-personnalisation-post-installation-pour-debian-13.sh |
Ajoute l’étape 8 (génération/dépôt de clés, durcissement, rollback) et les fonctions utilitaires associées. |
tests/test-fonctions.sh |
Ajoute des tests couvrant validation de ports, reconnaissance/validation de clés publiques et helpers authorized_keys. |
README.md |
Documente l’étape 8 et ses scénarios (client/serveur/les deux), ainsi que le garde-fou anti-lockout. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| command -v base64 >/dev/null 2>&1 || return 1 | ||
| n=${#type} | ||
| (( n > 0 && n < 256 )) || return 1 | ||
| b64="$(printf '%b%s' "\\0000\\0000\\0000\\0$(printf '%03o' "$n")" "$type" | base64 2>/dev/null | tr -d '\n')" || return 1 |
There was a problem hiding this comment.
Faux positif : la remarque confond deux syntaxes d'échappement octal. Dans une chaîne de format printf, l'octal s'écrit \nnn (3 chiffres max) — c'est bien la limite évoquée. Mais ici l'octal est passé à %b, qui utilise la forme \0nnn : un 0 d'introduction suivi de jusqu'à trois chiffres octaux. \0000 est donc \0 + 000, soit un unique octet NUL, et non « NUL + caractère 0 ».
Vérification sur les octets réellement produits :
$ printf '%b%s' "\0000\0000\0000\0$(printf '%03o' 11)" "ssh-ed25519" | od -An -tx1
00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
$ awk '{print $2}' cle.pub | base64 -d | head -c 15 | od -An -tx1
00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
Octet pour octet identique au blob d'une vraie clé, sans 0x30 parasite.
Le cas est par ailleurs déjà verrouillé par les tests, sur de vraies clés publiques et non des chaînes inventées : tests/test-fonctions.sh compare le préfixe calculé aux valeurs attendues pour trois types (AAAAC3NzaC1lZDI1NTE5, AAAAB3NzaC1y, AAAAE2VjZHNhLXNoYTItbmlzdHA1), et le cas ko "type rsa sur un corps ed25519" échouerait précisément si le préfixe était faussé — le contrôle croisé laisserait alors passer n'importe quelle incohérence type ↔ blob. Les 138 tests passent.
Aucun changement apporté.
Generated by Claude Code
| if systemctl is-active --quiet ssh.service 2>/dev/null; then | ||
| systemctl reload ssh.service >/dev/null 2>&1 && return 0 | ||
| systemctl restart ssh.service >/dev/null 2>&1 && return 0 | ||
| return 1 | ||
| fi | ||
| systemctl try-reload-or-restart ssh.service >/dev/null 2>&1 || true | ||
| return 0 |
There was a problem hiding this comment.
Corrigé en 18d9b26.
Précision sur le fond : try-reload-or-restart n'agit que sur une unité déjà démarrée et ne pouvait donc pas lancer ssh.service ni entrer en conflit avec ssh.socket. Mais la remarque vise juste sur la lisibilité — cette ligne contredisait frontalement le commentaire placé au-dessus, et il faut connaître la sémantique du préfixe try- pour comprendre qu'elle ne fait rien.
Le cas inactif est maintenant un retour explicite, avec le raisonnement écrit noir sur blanc : en activation par socket c'est ssh.socket qui tient le port (le démarrer serait effectivement une erreur), et sans activation par socket un service arrêté signifie qu'aucun sshd ne tourne — la configuration sera lue à son prochain démarrage. Dans les deux cas il n'y a rien à faire, et plus aucune commande n'est émise.
Generated by Claude Code
ssh_reload_config appelait « systemctl try-reload-or-restart ssh.service » dans le cas inactif. C'est certes un no-op pour systemd sur une unité arrêtée, mais la ligne contredisait le commentaire juste au-dessus et laissait craindre un démarrage de ssh.service concurrent de ssh.socket sur le même port. Le cas inactif est désormais un retour explicite, commenté : en activation par socket c'est ssh.socket qui tient le port, et sans activation par socket un service arrêté signifie qu'aucun sshd ne tourne — la configuration sera lue à son prochain démarrage. Relevé en revue par Copilot sur la PR #3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbhHGjLvttKAauNJjPJMuq
Pourquoi
Le script sécurisait SSH (port,
PermitRootLogin) mais laissait le mot de passe comme seul moyen de connexion. Une nouvelle étape 8/8 met en place l'authentification par clé, dans un sens ou dans l'autre selon le rôle de la machine — l'objectif étant de pouvoir se connecter avec une clé privée dès la fin de l'exécution.L'étape démarre par un menu : Serveur / Client / Les deux (poste rebond) / Ignorer / Explication. Le reste du script est inchangé, à la renumérotation près des bannières
x/7→x/8.Machine cliente — génération d'une paire de clés
ed25519recommandé,rsa 4096,ecdsa 521,ed25519-skpour un jeton FIDO2), emplacement, nom du fichier et commentaire au choix, tous validés.ssh-keygenlui-même, jamais passée via-N: la ligne de commande d'un processus est lisible de tous parps.runuser(droits corrects dès la création), avec repli en root suivi d'unchown; repli proposé sured25519si aucun jeton FIDO2 ne répond.~/.ssh/config(Host/IdentityFile/IdentitiesOnly/AddKeysToAgent), envoi parssh-copy-idpuis test de connexion réel.Serveur — dépôt d'une clé publique
https, compte GitHub (/<login>.keys), clé générée à l'instant, ou aide affichant les commandes à lancer sur le poste client.700/600et propriétaire corrigés.StrictModes— un home ou un.sshaccessible en écriture au groupe fait ignorer la clé sans aucun message — et vérification de l'AuthorizedKeysFileréellement en vigueur.Durcissement et garde-fou anti-lockout
PubkeyAuthentication yesest posé systématiquement. La coupure du mot de passe (PasswordAuthenticationetKbdInteractiveAuthentication, sans quoi elle serait illusoire sur Debian) n'est proposée qu'après une connexion prouvée ou une confirmation explicite, et le résultat est vérifié parsshd -T: si un fichier desshd_config.d/numéroté avant le nôtre l'emporte, il est nommé au lieu d'annoncer un succès qui n'a pas eu lieu.Sur le modèle éprouvé de
ip-fixe-confirmer,ssh-cles-rollbacketssh-cles-confirmersont installés dans/usr/local/sbin, et une minuteriesystemd-runréactive le mot de passe faute de confirmation dans le délai choisi.Ajoute aussi
backup_file_once, pour qu'une seconde sauvegarde du même fichiersshdn'écrase pas la copie d'origine par la version déjà modifiée.Tests
30 nouveaux cas sur les fonctions pures (138 au total, 0 échec), fondés sur de vraies clés publiques plutôt que sur des chaînes inventées.
Vérifications passées localement (celles de
qualite.yml) :bash -n,shellcheck -s bash -e SC2317,tests/test-fonctions.sh, bit exécutable.Tests fonctionnels réels menés avec un
sshdde test :ed25519etrsa700/600/644, bon propriétaire, relecture parssh-keygenAuthenticated using "publickey")~/.ssh/config600, relu parssh -GStrictModes.sshen707détecté et corrigéhttps, clé invalide, clé privée colléePasswordAuthentication nosshd -Tssh-cles-rollbackssh-cles-confirmerRestent à valider sur une vraie Debian 13 (pas de systemd dans l'environnement de test) : l'armement de la minuterie
systemd-runet le comportement en activation parssh.socket.🤖 Generated with Claude Code
https://claude.ai/code/session_01PbhHGjLvttKAauNJjPJMuq
Generated by Claude Code