Introduction
Ce retour d’expérience aborde un problème fréquent lors d’un bootstrap de serveur : appliquer des durcissements SSH (modification de port, options du daemon, règles de pare-feu) tout en conservant une voie d’administration. Le sujet vise une séquence ordonnée pour éviter de se couper soi‑même de la machine distante.
Symptômes
- Le changement concernait simultanément le daemon, le port et le pare-feu.
- Une erreur d’ordre aurait rendu la cible inaccessible.
Contexte
Le bootstrap devait durcir une machine distante tout en conservant une voie de contrôle pendant la transition. Le playbook observé contient des vérifications préalables (système d’exploitation et présence des clés publiques) et des étapes pour préparer le pare-feu et la configuration SSH avant de fermer les accès faibles.
Diagnostic
La séquence dans le playbook montre l’importance d’expliciter les dépendances temporelles entre la disponibilité d’une clé, l’ouverture d’une règle réseau pour le nouveau port, la validation de la configuration SSH et le redémarrage effectif du listener. Des tâches présentes incluent la vérification d’existence des clés sur la machine de contrôle, l’autorisation du nouveau port dans le pare-feu avant la modification du fichier de configuration, l’utilisation de validate pour tester la syntaxe SSH, l’appel des handlers puis une attente explicite que le service écoute sur le nouveau port.
# Exemple générique de séquence Ansible (représentation pédagogique)
- name: Allow new SSH port in firewall
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
- name: Update /etc/ssh/sshd_config to set new Port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^\s*#?\s*Port\s+'
line: "Port {{ ssh_port }}"
notify: Reload systemd and restart SSH socket
- name: Flush handlers to apply SSH reloads immediately
ansible.builtin.meta: flush_handlers
- name: Wait for SSH to listen on the new port
ansible.builtin.wait_for:
host: "{{ ansible_host }}"
port: "{{ ssh_port }}"
timeout: 30
delegate_to: localhost
- name: Enable firewall (deny incoming by default)
community.general.ufw:
state: enabled
policy: deny
direction: incoming
Cause racine
La cause racine identifiée est l’absence d’ordonnancement explicite des dépendances temporelles entre la clé d’administration, la règle réseau, la configuration SSH validée et la remise en service du listener. Sans cette ordonnancement, une modification de configuration pouvait rendre immédiat et irréversible l’isolement du système.
Solution retenue
- Valider la présence des clés publiques d’administration sur la machine de contrôle (
admin_authorized_keys). - Ouvrir le nouvel accès au niveau pare-feu (autoriser le
ssh_port) avant toute modification du daemon. - Appliquer les modifications du fichier
/etc/ssh/sshd_configavec une validation syntaxique (sshd -t) et déclencher les handlers de reload/restart sans fermer la session courante. - Forcer l’application des handlers (flush) pour s’assurer que le listener est redémarré avec la nouvelle configuration.
- Attendre explicitement que le service écoute sur le nouveau port (par une tâche d’attente déléguée au noeud de contrôle).
- Une fois le nouveau chemin vérifié, retirer ou durcir les accès faibles (par exemple désactiver l’authentification par mot de passe ou fermer l’ancien port).
Validation
La validation prévue comprend de tester la transition depuis une seconde session et de pouvoir revenir en arrière si nécessaire. La mise en attente que le nouveau port soit à l’écoute sert de garde‑fou avant de fermer les accès existants. Ces éléments figurent explicitement comme validations à réaliser dans les sorties et la procédure de bootstrap.
Enseignements
- Ordonnancer une transition de sécurité comme une migration réversible.
- Tester le nouveau chemin avant de supprimer l’ancien.
Ordonnancer une transition de sécurité comme une migration réversible.
Pattern réutilisable
Le pattern applicable aux opérations de basculement SSH est : ouvrir d’abord la nouvelle voie d’accès, appliquer et valider la configuration du service, attendre que le listener soit effectif, puis retirer l’ancien chemin. Ce pattern minimise la fenêtre d’indisponibilité et transforme le durcissement en une migration réversible.
Conclusion
La combinaison de vérifications préalables, d’une autorisation préalable du nouveau port, de validations syntaxiques et d’une attente explicite du listener réduit fortement le risque de perte d’accès. Notes opérationnelles importantes : ne jamais exécuter cette procédure en premier sur une cible unique et toujours tester la transition depuis une seconde session.