Aller au contenu principal

Monitors HTTP, marque blanche, domaine personnalisé. En ligne en 3 minutes.

Monitors & groupes

  • Monitors HTTP illimités
  • Groupement par service ou région

100 % personnalisable

  • Logo & palette de couleurs
  • Titre & méta SEO
  • Contenu libre
Nouveau Nouvelle fonctionnalité

Marque blanche

  • Aucune mention CaptainDNS
  • Votre domaine via CNAME
  • TLS automatique

Temps réel & historique

  • Synchronisé avec vos monitors
  • Historique 30 jours
  • Incidents & maintenances

Valider DKIM dans votre CI/CD : prévenir les records cassés en prod

Par CaptainDNS
Publié le 19 mai 2026

Pipeline CI/CD avec validation DKIM : hooks pre-commit, GitHub Actions, GitLab CI, Terraform et smoke test post-deploy
TL;DR
  • Un record DKIM cassé passe rarement les tests fonctionnels mais casse la délivrabilité dès le premier mail post-deploy
  • Les linters locaux détectent la syntaxe ; l'API CaptainDNS /v1/dkim/validate vérifie l'état réel publié dans le DNS
  • GitHub Actions, GitLab CI et les hooks Terraform peuvent bloquer un merge ou un apply sur un score DKIM insuffisant
  • La validation pre-commit économise du temps CI mais ne remplace pas un check sur l'état DNS publié
  • Combiner les validations SPF, DKIM et DMARC dans un même stage évite les régressions en cascade

Vous versionnez votre DNS dans Git, vos rotations de clés DKIM passent par une pull request, et tout se passe bien jusqu'au jour où Gmail commence à classer vos mails en spam. La cause : une balise p= tronquée par un provider DNS, ou une signature qui ne correspond plus au sélecteur publié. Le bug n'apparaît pas dans terraform plan, ni dans les tests d'intégration applicatifs.

La parade tient en un mot : shift-left. Plutôt que de découvrir le problème en production, validez chaque modification DKIM avant le merge, puis avant l'apply, puis après le déploiement. Cet article montre comment intégrer ces contrôles dans GitHub Actions, GitLab CI, Terraform et un hook pre-commit, avec des exemples copy-pastables.

Pour vérifier rapidement la syntaxe d'un enregistrement, le DKIM Syntax Check fournit une API publique réutilisable dans n'importe quel pipeline.

Pourquoi un record DKIM cassé arrive en production

Quatre causes principales expliquent qu'un DKIM invalide franchisse les défenses standard d'un déploiement DNS.

Manipulation manuelle des records DNS. Un opérateur copie une clé publique depuis un fournisseur transactionnel, oublie un caractère, ou intervertit deux blocs base64. Sans validation syntaxique, l'enregistrement est publié tel quel.

Rotation de clé sans validation. La nouvelle paire est générée, la clé publique est encodée, mais le pipeline de rotation ne re-vérifie pas que la chaîne base64 reste cohérente après l'export. Une troncature à 254 caractères dans certains panneaux d'administration suffit à casser la signature.

Provider DNS qui split en deux chunks. Une chaîne TXT supérieure à 255 caractères doit être fragmentée par le serveur DNS, qui doit la concaténer avec des guillemets. Certains providers (ou certaines UI) coupent la chaîne sans guillemets : le résolveur reçoit alors deux chaînes distinctes, et la clé reconstituée est invalide.

Pas de canary post-deploy. Le bug n'apparaît que sur les premiers mails envoyés après le changement. Le temps de remonter l'information (rapport DMARC, ticket support), des milliers de messages ont déjà été classés en spam.

Shift-left : valider avant le merge

Le principe est simple : toute modification d'un record DNS lié au mail passe par une validation automatique en pull request. Vous avez deux options pour valider.

Linter local open source. Des outils comme dkim-checker (CLI Go basé sur github.com/emersion/go-msgauth/dkim) ou des scripts maison vérifient la syntaxe d'un enregistrement, la cohérence des balises (v=DKIM1, k=rsa, p=...) et la longueur de la clé. Avantage : zéro dépendance réseau, rapide. Inconvénient : ne sait pas si la clé est effectivement publiée et accessible.

Linter cloud via API. L'endpoint CaptainDNS /v1/dkim/validate accepte un domaine et un sélecteur, interroge le DNS publié, parse l'enregistrement et renvoie un score normalisé, un état (valid, invalid, warning) et des recommandations. Avantage : valide l'état réel observé depuis l'extérieur, comme un MX recevant le mail. Inconvénient : dépend du réseau, du quota API et du DNS.

CritèreLinter localAPI CaptainDNS
Syntaxe (v=, k=, p=)OuiOui
Clé publiée dans le DNSNonOui
Taille effective de la cléPartielleOui
Score normaliséNonOui (calculé côté backend)
Dépendance réseauNonOui
Cas d'usagepre-commit, lint syntaxePR validation, post-deploy

Note importante : le score, les seuils et les recommandations sont calculés par l'API CaptainDNS côté backend. Le client CI consomme une valeur déjà normalisée, il ne refait aucun calcul.

Combinez les deux : linter local en pre-commit pour les itérations rapides, API en CI pour la validation finale avant le merge.

Pipeline GitHub Actions pour valider une pull request DNS

Voici un workflow qui se déclenche à chaque pull request modifiant des fichiers DNS. Il extrait les enregistrements DKIM, appelle l'API CaptainDNS, et bloque le merge si l'état est invalid ou si le score est inférieur à 50.

name: Validate DKIM records

on:
  pull_request:
    paths:
      - 'dns/**.tf'
      - 'dns/**.yaml'

jobs:
  validate-dkim:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Detect changed DNS files
        id: changes
        uses: dorny/paths-filter@v3
        with:
          filters: |
            dns:
              - 'dns/**'

      - name: Validate DKIM via CaptainDNS API
        if: steps.changes.outputs.dns == 'true'
        env:
          CDNS_API_KEY: ${{ secrets.CAPTAINDNS_API_KEY }}
        run: |
          set -euo pipefail
          for entry in $(yq '.dkim_selectors[]' dns/email.yaml); do
            domain=$(echo "$entry" | yq '.domain')
            selector=$(echo "$entry" | yq '.selector')

            response=$(curl -sS -X POST \
              -H "Authorization: Bearer ${CDNS_API_KEY}" \
              -H "Content-Type: application/json" \
              -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\"}" \
              https://api.captaindns.com/public/v1/dkim/validate)

            state=$(echo "$response" | jq -r '.state')
            score=$(echo "$response" | jq -r '.score')

            echo "Selector ${selector} for ${domain}: state=${state}, score=${score}"

            if [ "$state" = "invalid" ] || [ "$score" -lt 50 ]; then
              echo "::error::DKIM validation failed for ${selector}._domainkey.${domain}"
              exit 1
            fi
          done

La clé API se provisionne depuis la console CaptainDNS, puis se stocke en secret GitHub (Settings > Secrets and variables > Actions). Utilisez une clé scopée dkim:read pour limiter le rayon d'action en cas de fuite.

Pipeline GitHub Actions validant un record DKIM via l'API CaptainDNS

GitLab CI : pipeline avec stage validate

Sur GitLab, la logique reste identique. Le fichier .gitlab-ci.yml ci-dessous ajoute un cache pour éviter de re-valider des records inchangés et envoie une notification Slack en cas d'échec.

stages:
  - validate
  - apply

validate-dkim:
  stage: validate
  image: alpine:3.20
  before_script:
    - apk add --no-cache curl jq yq bash
  cache:
    key: dkim-validation-$CI_COMMIT_REF_SLUG
    paths:
      - .dkim-cache/
  script:
    - mkdir -p .dkim-cache
    - |
      bash -c '
      set -euo pipefail
      for entry in $(yq ".dkim_selectors[]" dns/email.yaml); do
        domain=$(echo "$entry" | yq ".domain")
        selector=$(echo "$entry" | yq ".selector")
        cache_key=".dkim-cache/${domain}_${selector}.json"

        response=$(curl -sS -X POST \
          -H "Authorization: Bearer ${CAPTAINDNS_API_KEY}" \
          -H "Content-Type: application/json" \
          -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\"}" \
          https://api.captaindns.com/public/v1/dkim/validate)

        echo "$response" > "$cache_key"
        state=$(echo "$response" | jq -r ".state")
        score=$(echo "$response" | jq -r ".score")

        if [ "$state" = "invalid" ] || [ "$score" -lt 50 ]; then
          curl -X POST -H "Content-Type: application/json" \
            -d "{\"text\":\"DKIM validation failed: ${selector}._domainkey.${domain} (score=${score})\"}" \
            "$SLACK_WEBHOOK_URL"
          exit 1
        fi
      done
      '
  only:
    changes:
      - dns/**/*

Le cache GitLab garde les réponses entre deux runs sur la même branche. Si un sélecteur n'a pas changé, l'API est toujours appelée (l'état DNS peut évoluer indépendamment du code), mais l'historique permet de tracer les régressions.

Terraform pre-apply : validation avant publication DNS

Quand votre DNS est géré par Terraform, Pulumi ou OpenTofu, la validation logique passe avant apply. Le hook ci-dessous extrait les TXT modifiés du plan JSON, les valide via l'API CaptainDNS, et bloque l'apply en cas d'échec.

#!/usr/bin/env bash
# scripts/dkim-preapply.sh
set -euo pipefail

terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Extraire les records TXT DKIM modifiés
jq -r '
  .resource_changes[]
  | select(.type == "cloudflare_record" or .type == "aws_route53_record")
  | select(.change.after.type == "TXT")
  | select(.change.after.name | test("_domainkey"))
  | "\(.change.after.name)|\(.change.after.value // (.change.after.records | join("")))"
' tfplan.json > dkim-changes.txt

while IFS='|' read -r fqdn value; do
  # fqdn : selector._domainkey.captaindns.com
  selector="${fqdn%%._domainkey.*}"
  domain="${fqdn#*._domainkey.}"

  response=$(curl -sS -X POST \
    -H "Authorization: Bearer ${CDNS_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\",\"record\":\"${value}\"}" \
    https://api.captaindns.com/public/v1/dkim/validate)

  state=$(echo "$response" | jq -r '.state')
  if [ "$state" = "invalid" ]; then
    echo "Pre-apply DKIM check failed: ${fqdn}"
    echo "$response" | jq '.recommendations'
    exit 1
  fi
done < dkim-changes.txt

terraform apply tfplan.binary

Branchez ce script sur un wrapper make plan && make validate && make apply, ou sur la stage Atlantis si vous utilisez ce pattern GitOps. La même logique s'applique à Pulumi (via pulumi preview --json) et OpenTofu, le format de plan JSON étant compatible.

Lint local en pre-commit

Pour économiser des minutes CI, ajoutez un hook pre-commit qui valide la syntaxe des records DKIM avant même le push. Le framework pre-commit.com permet d'orchestrer plusieurs hooks Python ou bash.

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dkim-syntax-lint
        name: DKIM syntax lint
        entry: scripts/dkim-lint.sh
        language: script
        files: ^dns/.*\.(tf|yaml)$
        pass_filenames: true

Le script dkim-lint.sh peut utiliser un parser local Go ou Python qui vérifie la présence des balises obligatoires (v=DKIM1, p=...) et la validité base64 de la clé publique. C'est suffisant pour rattraper 80% des erreurs de copier-coller.

Limite à connaître : un lint pre-commit ne peut pas vérifier l'état réel publié dans le DNS, ni les conflits avec un sélecteur déjà actif. La validation API en CI reste indispensable pour ces cas.

Tests d'intégration post-deploy

Une fois le DNS appliqué, vérifiez que la chaîne de bout en bout fonctionne. Un smoke test envoie un mail réel et vérifie l'en-tête Authentication-Results.

#!/usr/bin/env bash
# scripts/dkim-smoke-test.sh
set -euo pipefail

# Envoyer un mail test via swaks
swaks --to test-canary@captaindns.com \
      --from noreply@captaindns.com \
      --server smtp.captaindns.com \
      --header "Subject: DKIM canary $(date +%s)" \
      --body "Canary message"

# Attendre 60 secondes que le mail soit reçu
sleep 60

# Vérifier l'en-tête Authentication-Results via IMAP ou un endpoint maison
result=$(curl -sS "https://canary.captaindns.com/last-mail/auth-results")

if ! echo "$result" | grep -q "dkim=pass"; then
  echo "Canary DKIM check FAILED: $result"
  # Rollback automatique
  terraform apply -auto-approve -var "dkim_selector=previous"
  exit 1
fi

Vous pouvez aussi utiliser des services synthetic comme Mail-Tester ou GlockApps, qui exposent une API pour récupérer les résultats d'authentification d'un mail envoyé à une adresse jetable. La boucle de feedback devient : envoi du mail, lecture du résultat, puis rollback DNS si DKIM échoue.

Pour une vérification ponctuelle sans pipeline, le DKIM Record Check interroge le DNS et affiche l'état complet du sélecteur en quelques secondes.

Combiner avec SPF / DMARC

Valider DKIM seul ne suffit pas. Un DKIM valid mais un SPF cassé entraîne un dmarc=fail sur les mails non signés par DKIM. Un DMARC mal aligné dégrade la délivrabilité même avec un DKIM parfait.

Le pipeline complet doit donc valider les trois protocoles dans un même stage :

validate-email-auth:
  stage: validate
  script:
    - bash scripts/validate-spf.sh dns/email.yaml
    - bash scripts/validate-dkim.sh dns/email.yaml
    - bash scripts/validate-dmarc.sh dns/email.yaml
    - bash scripts/validate-bimi.sh dns/email.yaml  # optionnel

Chaque script appelle l'endpoint correspondant : /v1/spf/validate, /v1/dkim/validate, /v1/dmarc/validate. Mutualiser le client HTTP, le parsing de réponse et les seuils dans un module commun évite la duplication.

Un stage CI unique validant SPF, DKIM, DMARC et BIMI en parallèle via l'API CaptainDNS

Plan d'action recommandé

  1. Inventaire : listez vos sélecteurs DKIM actifs par domaine et sous-domaine
  2. Provisionner une clé API CaptainDNS scopée dkim:read + spf:read + dmarc:read
  3. Ajouter le hook pre-commit pour le lint syntaxe local
  4. Mettre en place un workflow CI qui valide les fichiers DNS modifiés en PR
  5. Brancher Terraform sur un hook pre-apply qui bloque les records invalides
  6. Activer un canary post-deploy : un mail test envoyé après chaque apply
  7. Étendre aux autres protocoles : SPF, DMARC, BIMI dans le même stage de validation

FAQ

Pourquoi valider DKIM dans la CI/CD ?

Un record DKIM cassé ne provoque pas d'erreur visible côté DNS. Il casse uniquement la délivrabilité des mails, ce qui se constate avec quelques heures de retard via les rapports DMARC ou les remontées utilisateurs. Valider en CI permet de bloquer le merge avant que des milliers de mails partent avec une signature invalide.

Quelle est la différence entre validation locale et API ?

Un linter local vérifie uniquement la syntaxe d'un enregistrement (balises, format base64). L'API CaptainDNS interroge le DNS publié, parse l'enregistrement tel qu'un récepteur le verrait, et renvoie un score calculé côté backend. Le local est rapide mais aveugle à l'état réel publié.

Comment intégrer la validation DKIM dans GitHub Actions ?

Créez un workflow déclenché sur les pull requests modifiant le dossier DNS. Récupérez les paires domaine/sélecteur, appelez l'endpoint /v1/dkim/validate via curl, parsez la réponse avec jq, et faites échouer le job si l'état est invalid ou si le score est inférieur à votre seuil (50 est une valeur de départ raisonnable).

Peut-on bloquer un terraform apply sur un DKIM invalide ?

Oui. Exécutez terraform plan -out=tfplan puis terraform show -json pour extraire les records TXT modifiés. Validez chaque enregistrement via l'API CaptainDNS avant de lancer terraform apply. Si la validation échoue, sortez du script avec un code non-zéro pour bloquer l'apply.

Faut-il aussi valider SPF et DMARC dans le même pipeline ?

Oui. DKIM, SPF et DMARC sont interdépendants. Un DKIM valide isolé ne garantit pas la délivrabilité si SPF échoue ou si DMARC n'est pas aligné. Validez les trois dans le même stage CI, avec un fail-fast sur le premier échec ou un rapport consolidé.

Quels secrets configurer pour l'API CaptainDNS dans la CI ?

Un seul secret suffit : CAPTAINDNS_API_KEY. Provisionnez-le depuis la console, scopez-le aux endpoints nécessaires (lecture DKIM/SPF/DMARC), et stockez-le dans le secret manager de votre CI. Évitez les clés admin dans un pipeline CI, qui n'a besoin que de lecture.

Que faire si le record passe localement mais échoue après deploy ?

Trois causes fréquentes : propagation DNS incomplète (attendez 30 minutes), provider DNS qui a split le TXT sans guillemets (vérifiez dig +short TXT selecteur._domainkey.domaine), ou cache négatif sur un résolveur intermédiaire. Le smoke test post-deploy doit réessayer avec un backoff exponentiel avant de déclencher un rollback.

Glossaire

  • Shift-left : pratique consistant à déplacer les contrôles de qualité vers les phases amont du cycle de développement (PR, pre-commit), plutôt que de les laisser en production.
  • Pre-commit hook : script exécuté localement avant chaque commit Git, qui peut bloquer le commit en cas d'erreur.
  • Pre-apply hook : script exécuté avant terraform apply (ou équivalent Pulumi/OpenTofu) qui valide les changements planifiés.
  • Canary : test post-déploiement avec un volume réduit qui détecte les régressions avant qu'elles n'affectent l'ensemble du trafic.
  • Score DKIM : valeur normalisée (0 à 100) calculée par l'API CaptainDNS à partir de la syntaxe, de la taille de clé, de l'algorithme et des recommandations RFC.
  • ARC (Authenticated Received Chain) : protocole RFC 8617 qui préserve les résultats d'authentification DKIM/SPF lors du transfert d'emails.

Testez votre intégration via le DKIM Syntax Check : l'outil expose la même API utilisée en CI/CD, idéal pour valider un record avant de l'ajouter à votre pipeline.


📚 Guides DKIM connexes à consulter

Sources

Articles similaires