BIND9 Hardened : Strategie de deploiement, tests et CI/CD

Cet article documente la chaîne qui valide et déploie le service DNS : les jeux de données de test, ce que la validation prouve réellement, et pourquoi la mise en production reste derrière un bouton.

Vue d'ensemble

flowchart LR
    subgraph dev["Developpement"]
        code["Dockerfile<br/>binaire d entree"]
    end
    subgraph build["Construction"]
        docker["Build multi-stage"]
    end
    subgraph test["Tests"]
        local["Test local"]
        parallel["Test parallele<br/>sur le routeur"]
    end
    subgraph ci["Integration continue"]
        lint["Analyse statique"]
        validate["Validation config<br/>et zones"]
        vues["Batterie de vues"]
    end
    subgraph prod["Production"]
        transfer["Telechargement"]
        swap["Bascule de tag"]
    end
    code --> docker --> local --> parallel --> transfer --> swap
    docker --> lint
    lint --> validate --> vues

Les zones ne sont pas écrites à la main

Les zones sont générées à partir de l'inventaire. Chaque hôte déclaré avec son adresse et son masque produit automatiquement le sous-réseau, la vue et les zones inverses correspondantes.

Les fichiers produits sont commités dans le dépôt, non pas comme source mais comme trace de ce qui est déployé : c'est ce qui permet de voir en revue de code qu'un changement d'inventaire fait apparaître ou disparaître une vue entière.

La clé de signature, elle, n'est jamais commitée. En local un exemplaire factice suffit ; en intégration continue, elle est générée aléatoirement à chaque exécution.

Ce que la validation prouve, et ce qu'elle ne prouvait pas

La validation monte la configuration dans un conteneur, vérifie la syntaxe et toutes les zones — directes comme inverses — puis démarre réellement le résolveur et l'interroge.

C'est nécessaire, et ça ne suffisait pas.

Dix-sept vues sur dix-neuf n'étaient jamais exercées

Le contrôle de configuration valide les zones. Une interrogation en boucle locale ne teste qu'une seule vue : celle que l'adresse source de la requête sélectionne.

Or chaque vue par sous-réseau n'autorise que les clients de son propre sous-réseau. Une requête venue d'ailleurs est refusée. Résultat : sur dix-neuf vues, dix-sept n'étaient jamais interrogées par la CI. Vérifié en retirant la zone interne d'une seule vue — la validation restait verte, et rien ne l'attrapait.

La parade consiste à poser une adresse du sous-réseau visé sur l'interface de boucle locale, puis à interroger en forçant l'adresse source. Le résolveur voit alors la requête comme venant de ce sous-réseau et sélectionne la vue correspondante. Chaque vue est ainsi exercée pour de bon.

Deux détails font la différence entre un test utile et un test décoratif :

  • Le test distingue « je n'ai pas pu tester » de « une vue ne résout pas ». Les deux sortent en rouge — un test qui ne tourne pas ne doit jamais passer pour vert — mais le message dit lequel des deux s'est produit, sinon on cherche une panne de DNS là où il manque un privilège réseau au runner.
  • Les noms interrogés sont passés en paramètre, pas codés dans le script. Le dépôt qui porte le script est public ; y inscrire une infrastructure réelle la ferait fuiter, et un défaut codé en dur finit toujours par pointer sur un hôte décommissionné sans que personne ne s'en aperçoive.

Tester en parallèle avant de basculer

Avant de toucher à la production, un conteneur de test tourne sur une adresse différente du routeur :

sequenceDiagram
    participant Dev as Poste
    participant VyOS as Routeur
    participant Prod as Resolveur de production
    participant Test as Resolveur de test
    Dev->>VyOS: telecharger l image
    Dev->>VyOS: demarrer sur une autre adresse
    Note over Prod: toujours actif
    Dev->>VyOS: interroger l instance de test
    VyOS->>Test: requete
    Test-->>VyOS: reponse
    Dev->>VyOS: supprimer l instance de test
    Dev->>VyOS: basculer le tag de production

Le conteneur de test ne reçoit aucun trafic réel : la zone de pare-feu n'autorise que l'adresse de production. Seules les requêtes émises depuis le routeur lui-même l'atteignent. C'est ce qui rend le test sans risque.

Deux workflows séparés, et c'est délibéré

La validation se déclenche sur les changements. Le déploiement, lui, ne part que sur un bouton — aucun événement automatique ne peut le lancer.

Pourquoi le déploiement reste manuel

Un déploiement réseau raté casse le DNS, et un DNS cassé isole le homelab — y compris la machine depuis laquelle on voudrait réparer. La porte manuelle n'est pas une précaution symbolique : c'est la seule chose qui garantit qu'un humain regarde le résultat de la validation avant que les zones ne partent.

La conséquence pratique est une règle sans exception : ces déploiements ne se lancent jamais à la main depuis un poste. Toujours par le pipeline, même quand on est pressé — et surtout quand on est pressé.

Trois points qui ont déjà mordu

Le job enchaîne la rotation de la clé, la synchronisation des zones, puis l'injection de la clé côté client ACME. Le détail vit dans le workflow ; ce qui mérite d'être retenu tient en trois pièges :

  • La clé est exclue de la synchronisation des zones et déployée par une étape distincte, parce que les deux ne suivent pas le même cycle de vie.
  • La synchronisation supprime les fichiers orphelins. C'est voulu : quand un sous-réseau disparaît, sa zone inverse doit disparaître aussi, sinon le résolveur continue de servir une zone qui ne correspond plus à rien.
  • Les journaux de zone ne sont pas exclus. Les exclure désynchronise les zones dynamiques, celles que le client ACME met à jour.

Basculer une version d'image

Le routeur télécharge lui-même l'image : il n'y a pas d'archive à transporter. Le tag, en revanche, ne vit pas dans un fichier de configuration mais dans la configuration persistante du routeur — la bascule est donc une transaction de configuration, pas une copie de fichier.

ansible-playbook image-update.yaml -i inventories/production --tags check
ansible-playbook image-update.yaml -i inventories/production --tags pull
ansible-playbook image-update.yaml -i inventories/production --tags apply

La phase finale se résume à poser le tag puis à valider la transaction. La validation arrête l'ancien conteneur et démarre le nouveau : la coupure DNS se compte en quelques secondes. Le détail des phases est dans la mise à jour des images.

Vérification après déploiement

La liste tient en sept points, et chacun vérifie quelque chose que les autres ne couvrent pas :

  • une résolution publique, qui prouve que la vue autoritative répond ;
  • une résolution interne, qui prouve que les zones locales sont chargées ;
  • une résolution inverse, qui est celle qu'on oublie de tester et qui casse le plus souvent ;
  • la version masquée, qui prouve que le durcissement est bien appliqué ;
  • le contrôle de santé du conteneur, qui doit sortir en succès ;
  • l'identité du processus, qui doit être non privilégiée avec le bon PID 1 ;
  • les journaux, qui ne doivent montrer aucune erreur au démarrage.

Liens

Articles connexes