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

Cet article documente la strategie complete de deploiement de l image BIND9 hardened : creation des jeux de donnees de test, validation, pipeline CI/CD et mise en production.

Vue d ensemble du pipeline

flowchart LR
    subgraph dev["Developpement"]
        code["Dockerfile<br/>init.go<br/>go.mod"]
    end
    subgraph build["Build"]
        docker["docker build<br/>multi-stage"]
    end
    subgraph test["Tests"]
        local["Test local<br/>docker run + dig"]
        parallel["Test parallele<br/>VyOS IP .11"]
    end
    subgraph ci["CI/CD"]
        lint["Hadolint<br/>ShellCheck"]
        validate["named-checkconf<br/>named-checkzone"]
        sign["cosign sign<br/>OIDC keyless"]
        scan["Trivy + Grype"]
    end
    subgraph prod["Production"]
        transfer["skopeo + scp<br/>podman load"]
        swap["config.boot<br/>restart container"]
    end
    code --> docker --> local --> parallel --> transfer --> swap
    docker --> lint
    lint --> validate --> sign --> scan

Jeux de donnees de test (JDD)

Structure des JDD

Les fichiers de test reproduisent la configuration de production avec des donnees sanitisees :

playbook/etc/bind/
    named.conf              # Config principale (statique)
    named.conf.options      # Forwarders, ACL, rate-limit
    named.conf.local        # Genere : vues, zones, includes
    db.jbsky.fr             # Zone publique
    db.home.arpa            # Zone interne (vue local)

<vue>_db.home.arpa      # Zone interne par vue
    db.
<subnet>             # Zones reverses

<vue>_db.<subnet>       # Symlinks reverses par vue
    zones.*.in-addr.arpa    # Snippets zone reverse
    zones.home.arpa         # Snippet zone forward
    zones.jbsky.fr          # Snippet zone publique + update-policy
    tsig.key                # Cle TSIG (generee, jamais committee)

Generation des JDD

Les zones sont generees par le playbook bind.yaml a partir de l inventaire Ansible. Chaque host avec une IP et un netmask genere automatiquement les sous-reseaux, les vues et les zones reverses :

ansible-playbook playbook/bind.yaml -i inventories/production

Cette commande produit ~35 fichiers dans playbook/etc/bind/ en environ 35 secondes. Les fichiers generes sont commites dans le repo comme trace de ce qui est deploye.

JDD de test TSIG

Pour les tests locaux, un tsig.key factice est cree :

key "tsig-key" {
    algorithm hmac-sha512;
    secret "dGVzdGtleWZvcmxvY2FsdGVzdGluZw==";
};

En CI, la cle est generee dynamiquement (64 octets aleatoires) par le job generate_tsig.

Tests

Test local (docker run)

Apres le build de l image, un test local valide le fonctionnement de base :

flowchart TB
    subgraph local["Test local sur ansible.home.arpa"]
        build["docker build<br/>bind9-hardened:9.20.24"]
        run["docker run<br/>config montee en volume"]
        check["Verification"]
    end
    build --> run
    run --> check
    check --> dig["dig jbsky.fr A"]
    check --> hc["init --healthcheck"]
    check --> ver["dig version.bind CH TXT"]
    check --> proc["docker top : UID 5300"]

Commandes de validation :

# Lancer le container de test
docker run -d --name bind9-test \
  --cap-add NET_BIND_SERVICE \
  -v /path/to/bind-config:/etc/bind \
  -v /path/to/cache:/var/cache/bind \
  -p 5353:53/udp -p 5353:53/tcp \
  jbsky/bind9-hardened:9.20.24

# Verifier le DNS
dig @127.0.0.1 -p 5353 jbsky.fr A +short
dig @127.0.0.1 -p 5353 pve.home.arpa A +short
dig @127.0.0.1 -p 5353 version.bind CH TXT +short

# Verifier le healthcheck
docker exec bind9-test /usr/local/bin/init --healthcheck

# Verifier le process (non-root)
docker top bind9-test
# Doit afficher UID 5300, PID 1 = tini, PID 2 = named

Test parallele sur VyOS

Avant de basculer la production, un container de test tourne en parallele sur une IP differente :

sequenceDiagram
    participant Dev as Poste dev
    participant VyOS as VyOS
    participant Prod as bind9 prod .10
    participant Test as bind9-test .11
    Dev->>VyOS: scp image.tar
    Dev->>VyOS: podman load
    Dev->>VyOS: podman run --ip .11
    Note over Prod: Toujours actif
    Note over Test: Container parallele
    Dev->>VyOS: dig @.11 jbsky.fr
    VyOS->>Test: DNS query
    Test-->>VyOS: Reponse OK
    Note over Dev: Validation OK
    Dev->>VyOS: podman rm bind9-test
    Dev->>VyOS: modifier config.boot
    VyOS->>Prod: restart container
    Note over Prod: Nouvelle image active

Le test parallele ne recoit pas de trafic reel (le firewall zone bind9 n autorise que l IP .10). Seules les requetes depuis VyOS lui-meme atteignent le container test.

Pipeline CI/CD GitLab

Stages

flowchart LR
    subgraph keygen["Stage keygen"]
        tsig["generate_tsig<br/>Cle TSIG aleatoire"]
    end
    subgraph validate["Stage validate"]
        check["validate_bind9<br/>checkconf + checkzone<br/>demarrage named<br/>dig de controle"]
    end
    subgraph deploy["Stage deploy"]
        bind["deploy_bind9<br/>rsync zones VyOS<br/>MANUEL"]
    end
    subgraph inject["Stage inject"]
        cert["deploy_certbot<br/>Injecte TSIG sur<br/>BIND9 + certbot"]
    end
    tsig --> check --> bind --> cert
    style bind fill:#f90,color:#fff

Le stage deploy est manuel : il necessite une validation humaine avant de pousser les zones en production.

Job validate_bind9 en detail

Le job de validation execute une serie de tests dans un container Alpine avec bind-tools :

Validation syntaxique  : named-checkconf
Validation zone jbsky  : named-checkzone jbsky.fr db.jbsky.fr
Validation zone home   : named-checkzone home.arpa db.home.arpa
Validation zones reverse : boucle sur tous les db.192.168.* et db.172.17.*
Test fonctionnel        : demarrage named + dig pve.home.arpa + dig jbsky.fr + dig MX

Si un test echoue, le pipeline s arrete et le deploiement est bloque.

Job deploy_bind9

Le deploiement utilise rsync pour synchroniser les fichiers de zone :

rsync -az --delete --chmod=D0755,F0644 \
  --exclude=tsig.key \
  playbook/etc/bind/ \
  vyos:/config/containers/bind9/

Points critiques :

  • --exclude=tsig.key : la cle est deployee separement par le job deploy_certbot
  • --delete : supprime les fichiers orphelins (important quand un subnet disparait)
  • Les fichiers .jnl ne sont PAS exclus (sinon desync avec les zones dynamiques)

Apres le rsync, le playbook :

  • Fixe les permissions (UID 5300 sur db.jbsky.fr, chmod 644 sur tsig.key)
  • Redemarre le container via restart container bind9
  • Attend le port 53
  • Execute des dig de controle

Deploiement d une nouvelle version d image

Workflow complet

flowchart TB
    A["Modifier Dockerfile ou init.go"] --> B["Build local"]
    B --> C{"Tests locaux OK ?"}
    C -->|"Non"| A
    C -->|"Oui"| D["docker save + scp vers VyOS"]
    D --> E["podman load sur VyOS"]
    E --> F["Test parallele IP .11"]
    F --> G{"dig + healthcheck OK ?"}
    G -->|"Non"| A
    G -->|"Oui"| H["Modifier config.boot<br/>image + capability"]
    H --> I["commit + save"]
    I --> J["Verification production"]
    J --> K["Backup VyOS"]
    K --> L["Push GitHub + GitLab"]
    style C fill:#f90,color:#fff
    style G fill:#f90,color:#fff

Transfer de l image vers VyOS

VyOS n a pas d acces Internet sortant. Le transfer se fait en 3 etapes :

# Sur le poste de build
docker save jbsky/bind9-hardened:9.20.24 -o /tmp/bind9.tar
scp /tmp/bind9.tar user@vyos:/tmp/

# Sur VyOS
podman load -i /tmp/bind9.tar
rm /tmp/bind9.tar

Bascule production

La modification de config.boot se fait via le wrapper VyOS natif :

set container name bind9 image docker.io/jbsky/bind9-hardened:9.20.24
set container name bind9 capability net-bind-service
delete container name bind9 environment BIND9_USER
commit
save

Le commit arrete l ancien container et demarre le nouveau avec la nouvelle image. Coupure DNS de 5 a 10 secondes.

Verification post-deploiement

Checklist de validation apres chaque deploiement :

  • Resolution forward : dig jbsky.fr A retourne l IP publique
  • Resolution interne : dig pve.home.arpa A retourne l IP Proxmox
  • Resolution reverse : `dig -x ` retourne le FQDN
  • Version masquee : dig version.bind CH TXT retourne "not disclosed"
  • Healthcheck : podman exec bind9 /usr/local/bin/init --healthcheck retourne 0
  • Process : UID 5300, PID 1 = tini, PID 2 = named
  • Logs : podman logs bind9 ne montre pas d erreur

Liens

Articles connexes


Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.