Vai al contenuto principale

Monitor HTTP, white label, dominio personalizzato. Online in 3 minuti.

Monitor e gruppi

  • Monitor HTTP illimitati
  • Raggruppamento per servizio o regione

100% personalizzabile

  • Logo e palette di colori
  • Titolo e meta SEO
  • Contenuto libero
Novità Nuova funzionalità

White label

  • Nessuna menzione di CaptainDNS
  • Il tuo dominio tramite CNAME
  • TLS automatico

Tempo reale e cronologia

  • Sincronizzato con i tuoi monitor
  • Cronologia 30 giorni
  • Incident e manutenzioni

Validare DKIM nella tua CI/CD: bloccare i record rotti prima della produzione

Di CaptainDNS
Pubblicato il 19 maggio 2026

Pipeline CI/CD con validazione DKIM: hook pre-commit, GitHub Actions, GitLab CI, Terraform e smoke test post-deploy
TL;DR
  • Un record DKIM rotto raramente fallisce i test funzionali ma rompe la deliverability dal primo email post-deploy
  • I linter locali rilevano la sintassi; l'API CaptainDNS /v1/dkim/validate verifica lo stato reale pubblicato nel DNS
  • GitHub Actions, GitLab CI e gli hook Terraform possono bloccare un merge o un apply su uno score DKIM insufficiente
  • La validazione pre-commit risparmia minuti CI ma non sostituisce un check sullo stato DNS pubblicato
  • Combinare le validazioni SPF, DKIM e DMARC in uno stesso stage evita regressioni a cascata

Tieni il tuo DNS versionato in Git, le tue rotazioni di chiavi DKIM passano da una pull request e tutto va bene finché un giorno Gmail inizia a classificare le tue mail come spam. La causa: un tag p= troncato da un provider DNS, oppure una firma che non corrisponde più al selettore pubblicato. Il bug non appare in terraform plan, né nei test di integrazione applicativi.

La risposta sta in una parola: shift-left. Invece di scoprire il problema in produzione, valida ogni modifica DKIM prima del merge, poi prima dell'apply, poi dopo il deploy. Questo articolo mostra come integrare questi controlli in GitHub Actions, GitLab CI, Terraform e un hook pre-commit, con esempi copy-paste.

Per verificare rapidamente la sintassi di un record, il DKIM Syntax Check fornisce un'API pubblica riutilizzabile in qualsiasi pipeline.

Perché un record DKIM rotto arriva in produzione

Quattro cause principali spiegano perché un DKIM invalido supera le difese standard di un deployment DNS.

Modifica manuale dei record DNS. Un operatore copia una chiave pubblica da un provider transazionale, dimentica un carattere, oppure scambia due blocchi base64. Senza validazione sintattica, il record viene pubblicato così com'è.

Rotazione di chiave senza validazione. La nuova coppia viene generata, la chiave pubblica codificata, ma la pipeline di rotazione non ricontrolla che la stringa base64 resti coerente dopo l'export. Un troncamento a 254 caratteri in alcuni pannelli di amministrazione basta a rompere la firma.

Provider DNS che fa split in due chunk. Una stringa TXT superiore a 255 caratteri deve essere frammentata dal server DNS, che la concatena con virgolette. Alcuni provider (o alcune UI) tagliano la stringa senza virgolette: il resolver riceve allora due stringhe distinte, e la chiave ricostruita è invalida.

Nessun canary post-deploy. Il bug appare solo sulle prime email inviate dopo la modifica. Quando l'informazione risale (report DMARC, ticket di supporto), migliaia di messaggi sono già finiti in spam.

Shift-left: validare prima del merge

Il principio è semplice: qualsiasi modifica di un record DNS legato alla mail passa per una validazione automatica in pull request. Hai due opzioni per validare.

Linter locale open source. Strumenti come dkim-checker (CLI Go basata su github.com/emersion/go-msgauth/dkim) o script fatti in casa verificano la sintassi di un record, la coerenza dei tag (v=DKIM1, k=rsa, p=...) e la lunghezza della chiave. Vantaggio: zero dipendenza di rete, veloce. Svantaggio: non sa se la chiave è effettivamente pubblicata e accessibile.

Linter cloud via API. L'endpoint CaptainDNS /v1/dkim/validate accetta un dominio e un selettore, interroga il DNS pubblicato, parsa il record e restituisce uno score normalizzato, uno stato (valid, invalid, warning) e raccomandazioni. Vantaggio: valida lo stato reale osservato dall'esterno, come un MX che riceve la mail. Svantaggio: dipende dalla rete, dal quota API e dal DNS.

CriterioLinter localeAPI CaptainDNS
Sintassi (v=, k=, p=)
Chiave pubblicata nel DNSNo
Dimensione effettiva della chiaveParziale
Score normalizzatoNoSì (calcolato lato backend)
Dipendenza di reteNo
Caso d'usopre-commit, lint sintassivalidazione PR, post-deploy

Nota importante: lo score, le soglie e le raccomandazioni sono calcolati dall'API CaptainDNS lato backend. Il client CI consuma un valore già normalizzato, non rifà alcun calcolo.

Combina le due cose: linter locale in pre-commit per iterazioni rapide, API in CI per la validazione finale prima del merge.

Pipeline GitHub Actions per validare una pull request DNS

Ecco un workflow che si attiva a ogni pull request che modifica file DNS. Estrae i record DKIM, chiama l'API CaptainDNS e blocca il merge se lo stato è invalid o se lo score è inferiore a 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 chiave API si genera dalla tua console CaptainDNS, poi si memorizza come secret GitHub (Settings > Secrets and variables > Actions). Usa una chiave con scope dkim:read per limitare il raggio d'azione in caso di leak.

Pipeline GitHub Actions che valida un record DKIM via l'API CaptainDNS

GitLab CI: pipeline con stage validate

Su GitLab la logica resta identica. Il file .gitlab-ci.yml qui sotto aggiunge una cache per evitare di rivalidare record invariati e invia una notifica Slack in caso di fallimento.

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/**/*

La cache GitLab conserva le risposte tra due run sullo stesso branch. Se un selettore non è cambiato, l'API viene comunque chiamata (lo stato DNS può evolvere indipendentemente dal codice), ma lo storico permette di tracciare le regressioni.

Terraform pre-apply: validazione prima della pubblicazione DNS

Quando il tuo DNS è gestito da Terraform, Pulumi o OpenTofu, la validazione logica avviene prima dell'apply. L'hook qui sotto estrae i TXT modificati dal piano JSON, li valida via l'API CaptainDNS e blocca l'apply in caso di fallimento.

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

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

# Estrarre i record TXT DKIM modificati
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

Aggancia questo script a un wrapper make plan && make validate && make apply, o allo stage Atlantis se usi questo pattern GitOps. La stessa logica vale per Pulumi (via pulumi preview --json) e OpenTofu, dato che il formato JSON del piano è compatibile.

Lint locale in pre-commit

Per risparmiare minuti CI, aggiungi un hook pre-commit che valida la sintassi dei record DKIM ancora prima del push. Il framework pre-commit.com permette di orchestrare più hook Python o 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

Lo script dkim-lint.sh può appoggiarsi su un parser locale Go o Python che verifica la presenza dei tag obbligatori (v=DKIM1, p=...) e la validità base64 della chiave pubblica. Basta per intercettare l'80 % degli errori di copia-incolla.

Limite da conoscere: un lint pre-commit non può verificare lo stato reale pubblicato nel DNS, né i conflitti con un selettore già attivo. La validazione API in CI resta indispensabile per questi casi.

Test di integrazione post-deploy

Una volta applicato il DNS, verifica che la catena end-to-end funzioni. Uno smoke test invia una mail reale e controlla l'header Authentication-Results.

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

# Inviare una mail di 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"

# Attendere 60 secondi che la mail arrivi
sleep 60

# Verificare l'header Authentication-Results via IMAP o un endpoint dedicato
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 automatico
  terraform apply -auto-approve -var "dkim_selector=previous"
  exit 1
fi

Puoi anche usare servizi sintetici come Mail-Tester o GlockApps, che espongono un'API per recuperare i risultati di autenticazione di una mail inviata a un indirizzo usa-e-getta. Il ciclo di feedback diventa: invio della mail, lettura del risultato, poi rollback DNS se DKIM fallisce.

Per una verifica puntuale senza pipeline, il DKIM Record Check interroga il DNS e mostra lo stato completo del selettore in pochi secondi.

Combinare con SPF / DMARC

Validare DKIM da solo non basta. Un DKIM valid ma uno SPF rotto produce un dmarc=fail sulle mail non firmate da DKIM. Un DMARC mal allineato peggiora la deliverability anche con un DKIM perfetto.

La pipeline completa deve quindi validare i tre protocolli in un unico 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  # opzionale

Ogni script chiama l'endpoint corrispondente: /v1/spf/validate, /v1/dkim/validate, /v1/dmarc/validate. Condividere il client HTTP, il parsing della risposta e le soglie in un modulo comune evita la duplicazione.

Un unico stage CI che valida SPF, DKIM, DMARC e BIMI in parallelo via l'API CaptainDNS

Piano d'azione raccomandato

  1. Inventario: elenca i tuoi selettori DKIM attivi per dominio e sottodominio
  2. Provisionare una chiave API CaptainDNS con scope dkim:read + spf:read + dmarc:read
  3. Aggiungere l'hook pre-commit per il lint sintassi locale
  4. Mettere in piedi un workflow CI che valida i file DNS modificati in PR
  5. Collegare Terraform a un hook pre-apply che blocca i record invalidi
  6. Attivare un canary post-deploy: una mail di test inviata dopo ogni apply
  7. Estendere agli altri protocolli: SPF, DMARC, BIMI nello stesso stage di validazione

FAQ

Perché validare DKIM nella CI/CD?

Un record DKIM rotto non provoca un errore visibile lato DNS. Rompe solo la deliverability delle mail, cosa che si constata con qualche ora di ritardo via i report DMARC o le segnalazioni degli utenti. Validare in CI permette di bloccare il merge prima che migliaia di mail partano con una firma invalida.

Qual è la differenza tra validazione locale e validazione via API?

Un linter locale verifica solo la sintassi di un record (tag, formato base64). L'API CaptainDNS interroga il DNS pubblicato, parsa il record come lo vedrebbe un ricevente e restituisce uno score calcolato lato backend. Il locale è veloce ma cieco allo stato reale pubblicato.

Come integrare la validazione DKIM in GitHub Actions?

Crea un workflow attivato sulle pull request che modificano la cartella DNS. Recupera le coppie dominio/selettore, chiama l'endpoint /v1/dkim/validate via curl, parsa la risposta con jq e fai fallire il job se lo stato è invalid o se lo score è inferiore alla tua soglia (50 è un valore di partenza ragionevole).

Si può bloccare un terraform apply su un DKIM invalido?

Sì. Esegui terraform plan -out=tfplan poi terraform show -json per estrarre i record TXT modificati. Valida ogni record via l'API CaptainDNS prima di lanciare terraform apply. Se la validazione fallisce, esci dallo script con un codice non zero per bloccare l'apply.

Bisogna validare anche SPF e DMARC nella stessa pipeline?

Sì. DKIM, SPF e DMARC sono interdipendenti. Un DKIM valido isolato non garantisce la deliverability se SPF fallisce o se DMARC non è allineato. Valida i tre nello stesso stage CI, con fail-fast sul primo fallimento o un report consolidato.

Quali secret configurare per l'API CaptainDNS in CI?

Basta un solo secret: CAPTAINDNS_API_KEY. Provisionalo dalla console, dagli lo scope sugli endpoint necessari (lettura DKIM/SPF/DMARC) e memorizzalo nel secret manager della tua CI. Evita le chiavi admin in una pipeline CI, che ha solo bisogno di lettura.

Cosa fare se il record passa localmente ma fallisce dopo il deploy?

Tre cause frequenti: propagazione DNS incompleta (aspetta 30 minuti), provider DNS che ha fatto split del TXT senza virgolette (verifica con dig +short TXT selettore._domainkey.dominio), o cache negativa su un resolver intermedio. Lo smoke test post-deploy deve riprovare con backoff esponenziale prima di scatenare un rollback.

Glossario

  • Shift-left: pratica che consiste nello spostare i controlli di qualità verso le fasi a monte del ciclo di sviluppo (PR, pre-commit), piuttosto che lasciarli in produzione.
  • Pre-commit hook: script eseguito localmente prima di ogni commit Git, che può bloccare il commit in caso di errore.
  • Pre-apply hook: script eseguito prima di terraform apply (o equivalente Pulumi/OpenTofu) che valida le modifiche pianificate.
  • Canary: test post-deployment con un volume ridotto che rileva le regressioni prima che colpiscano tutto il traffico.
  • Score DKIM: valore normalizzato (0 a 100) calcolato dall'API CaptainDNS a partire dalla sintassi, dalla dimensione della chiave, dall'algoritmo e dalle raccomandazioni RFC.
  • ARC (Authenticated Received Chain): protocollo RFC 8617 che preserva i risultati di autenticazione DKIM/SPF durante l'inoltro delle email.

Testa la tua integrazione con il DKIM Syntax Check: lo strumento espone la stessa API utilizzata in CI/CD, ideale per validare un record prima di aggiungerlo alla tua pipeline.


Guide DKIM correlate da consultare

Fonti

Articoli simili