Sécuriser les changements nftables avec un retour arrière programmé

Sécuriser les changements nftables avec un retour arrière programmé

Sécuriser les changements nftables avec un retour arrière programmé

Lorsqu’on administre un serveur dédié à distance, une erreur fréquente après une modification du pare-feu consiste à considérer les règles comme validées parce que la session SSH en cours permet encore d’exécuter des commandes. La réalité peut être tout autre : le suivi des connexions maintient l’ancienne session, alors que les nouvelles connexions sont déjà bloquées. Une procédure fiable consiste à sauvegarder l’état initial, à programmer une restauration, à charger les règles, puis à vérifier l’accès d’administration avec une connexion indépendante.

Cet article utilise Debian 12, nftables et systemd pour illustrer une modification ciblée qui limite uniquement les sources autorisées à se connecter en SSH. Pour les machines AMDSSH, l’adresse de connexion et les identifiants sont consultables dans la console après le paiement ; si plusieurs adresses IPv4 sont disponibles, SSH utilise la première. L’exemple ne présuppose ni nom de compte ni port et ne définit pas une politique complète pour les ports des services.

1. Définir le périmètre de gestion des règles

Les opérations suivantes nécessitent un compte disposant des droits sudo, un système sur lequel nftables est installé et une gestion centralisée des règles par nftables natif. Si des réseaux de conteneurs, des ponts de virtualisation ou d’autres outils de pare-feu génèrent dynamiquement des règles, il ne faut pas appliquer telle quelle une restauration par instantané complet : le retour arrière pourrait écraser les éléments ajoutés par ces outils pendant la modification. Dans ce cas, il faut d’abord identifier le gestionnaire responsable des règles, puis suivre sa procédure de modification.

Commencez par examiner l’état actuel :

sudo nft list ruleset
sudo systemctl cat nftables.service
command -v nft

Vérifiez les règles existantes, le fichier chargé au démarrage et le chemin réel de nft. Cet article utilise une table distincte, inet ops_guard. Si une table porte déjà ce nom, vous devez vérifier son usage afin de ne pas écraser les règles d’une autre tâche.

Le minuteur de retour arrière ne traite que cette modification des règles du pare-feu. Il ne peut pas réparer un incident dû à une modification simultanée du routage, du service SSH ou de la source d’administration. Ne modifiez qu’une variable à la fois.

Lire les paramètres réels de la connexion SSH

Exécutez ces commandes dans le Bash de la session distante actuelle, sans passer au préalable dans une autre session qui ne conserverait pas les variables d’environnement de la connexion :

test -n "$SSH_CONNECTION" || exit 1
read -r ADMIN_IP CLIENT_PORT SERVER_IP SSH_PORT <<< "$SSH_CONNECTION"
case "$ADMIN_IP" in
  *:*) printf '%s\n' 'IPv4 SSH session required'; exit 1 ;;
esac
printf 'source=%s destination=%s port=%s\n' \
  "$ADMIN_IP" "$SERVER_IP" "$SSH_PORT"

L’adresse obtenue est la source de la connexion telle que le serveur la voit. Il peut s’agir de l’adresse de sortie du réseau de votre entreprise ou de celle d’un bastion, et non de l’adresse privée de votre poste de travail. Si cette adresse de sortie peut changer, mettez d’abord en place un chemin d’administration stable. Cet exemple exige que la session actuelle utilise IPv4 ; ne générez pas les règles depuis une session IPv6 pour continuer malgré cette incompatibilité.

2. Sauvegarder un instantané et programmer la restauration automatique

L’instantané doit contenir une instruction qui supprime les anciennes règles, sinon la restauration risque de rencontrer des objets déjà présents. Le répertoire ne doit être accessible qu’aux administrateurs, afin que les comptes ordinaires ne puissent pas consulter le plan d’adressage contenu dans les règles.

sudo install -d -m 700 /root/firewall-change
sudo sh -c '
set -e
umask 077
{
  printf "flush ruleset\n"
  nft list ruleset
} > /root/firewall-change/before.nft
'
sudo nft -c -f /root/firewall-change/before.nft

L’option -c vérifie les règles sans les charger. Elle peut détecter des erreurs de syntaxe et de références dans les règles, mais ne prouve pas que l’accès distant reste possible. Si cette vérification échoue, interrompez la modification ; ne contournez pas l’erreur pour poursuivre.

Après avoir confirmé qu’aucune tâche du même nom ne subsiste, programmez une restauration dans trois minutes :

sudo systemctl status ops-firewall-rollback.timer
sudo systemd-run \
  --unit=ops-firewall-rollback \
  --on-active=3m \
  "$(command -v nft)" \
  -f /root/firewall-change/before.nft
sudo systemctl list-timers ops-firewall-rollback.timer

Il est normal que la première commande indique que l’unité n’existe pas. Si une tâche existe déjà, déterminez d’abord son origine. Les trois minutes correspondent au délai de retour arrière de cette opération, et non à un engagement de livraison du service. Vous devez constater que l’exécution du minuteur est bien programmée avant de passer au chargement. Ne modifiez pas d’autres règles réseau pendant ce délai : la restauration complète annulerait également ces modifications.

3. Restreindre les sources d’administration avec une table distincte

Lors de la première utilisation, créez une table vide. N’exécutez la commande de création qu’après avoir confirmé qu’aucune table du même nom n’existe ; si la table dédiée à cette opération est déjà présente, sautez cette étape :

sudo nft add table inet ops_guard

Générez ensuite le fichier de règles proposé :

sudo tee /root/firewall-change/candidate.nft > /dev/null <<EOF
delete table inet ops_guard
table inet ops_guard {
  chain input {
    type filter hook input priority 10; policy accept;
    iifname "lo" accept
    ct state established,related accept
    ip saddr $ADMIN_IP tcp dport $SSH_PORT accept
    tcp dport $SSH_PORT counter drop
  }
}
EOF
sudo chmod 600 /root/firewall-change/candidate.nft
sudo nft -c -f /root/firewall-change/candidate.nft
sudo nft -f /root/firewall-change/candidate.nft
sudo nft -a list table inet ops_guard

La suppression et la recréation décrites dans le fichier sont appliquées au sein d’une seule transaction nftables : il ne s’agit pas de tout vider, puis d’ajouter les règles une à une. Chaque chargement de ce fichier exige que la table dédiée existe déjà. Il ne peut donc pas servir directement de configuration pour un démarrage à froid.

Les règles autorisent le trafic de boucle locale et les connexions déjà établies. Elles permettent ensuite à la source IPv4 d’administration actuelle d’accéder au port SSH réellement utilisé, puis bloquent l’accès à ce port depuis les autres sources. La dernière règle correspond aussi au trafic IPv6 : cet exemple n’autorise donc pas les nouvelles connexions SSH en IPv6. Si vous avez besoin d’un accès d’administration en IPv6, ajoutez au préalable une règle d’autorisation explicite avec ip6 saddr et testez-la séparément.

Cette table ne restreint pas les autres ports des services. Par ailleurs, un verdict accept dans cette table ne peut pas annuler un drop dans une autre chaîne de base : les nouvelles connexions peuvent encore être bloquées par des règles existantes. Ne considérez pas cet exemple comme un socle de sécurité complet et ne vous en servez surtout pas pour supprimer les politiques existantes des services.

4. Décider de valider la modification à partir d’une nouvelle connexion indépendante

Gardez la session initiale ouverte et lancez une autre connexion depuis la source autorisée. Si le client utilise le multiplexage des connexions, désactivez-le ; sinon, une nouvelle fenêtre pourrait réutiliser l’ancienne connexion TCP.

Exécutez les commandes suivantes dans le Bash du client et saisissez, à chaque invite, les paramètres fournis dans la console :

read -r -p 'Host: ' HOST
read -r -p 'User: ' LOGIN
read -r -p 'Port: ' PORT
ssh -o ControlMaster=no -o ControlPath=none \
  -p "$PORT" "$LOGIN@$HOST"

La validation doit comprendre au moins trois vérifications :

Une simple erreur d’authentification par mot de passe ou par clé ne prouve pas que la restriction des sources a échoué. Déterminez à quelle couche la connexion a été refusée. N’assouplissez pas temporairement les règles pour effectuer les tests.

Si la connexion indépendante échoue, conservez l’état en place et attendez le retour arrière, au lieu d’ajouter successivement des règles d’autorisation. Après la restauration, consultez les journaux du service associé au minuteur, puis analysez l’adresse source, le port et l’effet des autres chaînes. Une fois les vérifications réussies, arrêtez le minuteur :

sudo systemctl stop ops-firewall-rollback.timer
sudo systemctl is-active ops-firewall-rollback.timer

Vérifiez également que le service de restauration n’a pas déjà démarré. Si le minuteur vient d’arriver à échéance, vous devez relire les règles actives : le succès de la commande d’arrêt ne suffit pas à conclure que la configuration proposée est toujours en place.

5. Séparer la persistance des règles de la validation au démarrage

Ne traitez le fichier de démarrage qu’après avoir validé les règles actives. Identifiez le chemin effectivement chargé à partir de la configuration du service examinée précédemment. Sous Debian 12, le chemin habituel est /etc/nftables.conf, mais un service personnalisé peut utiliser un autre fichier.

Lorsque les règles sont stables et qu’aucun autre gestionnaire ne les modifie, exportez l’ensemble des règles actives dans un fichier contenant flush ruleset, vérifiez-le avec nft -c -f, puis sauvegardez le fichier de démarrage d’origine avant de le remplacer. Ne sauvegardez pas uniquement la table dédiée : des règles existantes nécessaires aux services pourraient manquer après le redémarrage.

La mise en place de la persistance constitue une deuxième modification. Programmez donc un nouveau retour arrière, chargez le fichier de démarrage complet, puis répétez les tests de connexion indépendante. Ne regroupez pas l’activation du service, le remplacement de la configuration et le redémarrage de la machine dans une seule commande. Planifiez séparément la validation après redémarrage et vérifiez les règles, la connexion depuis la source autorisée et la connectivité des services.

Consignez enfin la source réelle, le port SSH, le fichier de règles proposé, l’instantané et les résultats des vérifications. La preuve de validation n’est pas « la commande a réussi », mais la réunion de trois conditions : une nouvelle connexion fonctionne, le chemin de blocage se comporte comme prévu et la configuration de démarrage permet de reproduire les règles.

Questions fréquentes

Une session SSH encore ouverte suffit-elle pour annuler le retour arrière ?

Non. Une règle de suivi d’état peut préserver la session existante. Il faut ouvrir une connexion indépendante depuis une source autorisée et réussir l’authentification avant d’annuler le minuteur.

Les règles proposées ferment-elles aussi les ports applicatifs ?

Non. Elles limitent uniquement les nouvelles connexions au port SSH réellement utilisé. Les autres ports restent soumis aux règles existantes ; cet exemple n’est pas une politique complète de refus par défaut.

Serveur dédié EPYC 7543Pdès $450.2/mois

Choisir une configuration complète selon les besoins

Le tarif mensuel total dépend du modèle et des options. La page de configuration affiche le détail complet ; les identifiants de connexion sont disponibles dans la console après la livraison.