DNS as code cu dnscontrol

Cum ziceam și în prima postare, mă uitam zilele astea peste soluții IaC pentru DNS, aflând inițial de octoDNS. Nu prea mi-a plăcut așa mult așa că am mai căutat și am dat de dnscontrol, dezvoltat aparent de cei de la Stack Exchange, care arată mai bine și avea o documentație mai bună.

Setup

În documentația lor au 4 variante de instalare, dar eu am mers direct pe varianta cu Docker pentru că voiam ceva simplu pe care îl puteam integra cu un pipeline de CI în Gitlab.

M-am uitat puțin peste ce provideri suportă și am aflat că suportă chiar și registrari, dar din păcate niciunul de acolo nu vinde domenii .ro. M-am dus după pe pagina aferentă Cloudflare și am creat întâi creds.json pentru credențiale (care probabil va fi un secret în pipeline-ul de CI):

{
  "cloudflare": {
    "TYPE": "CLOUDFLAREAPI",
    "accountid": "your-cloudflare-account-id",
    "apitoken": "your-cloudflare-api-token"
  }
}

Am creat un API token destul de simplu (aveau și unele permisiuni opționale pt Page Rules sau Single Redirect, features pe care nu le folosesc):

Apoi am făcut un fișier dnsconfig.js de test, să văd că sunt bune credențialele:

var REG_NONE = NewRegistrar("none");
var DSP_CLOUDFLARE = NewDnsProvider("cloudflare");

D("chitzu.ro", REG_NONE, DnsProvider(DSP_CLOUDFLARE),
    A("test","1.2.3.13"),
);

Și am rulat imaginea de docker să văd cum arată:

$ docker run --rm -it -v "$(pwd):/dns"  ghcr.io/stackexchange/dnscontrol preview
CONCURRENTLY gathering 1 zone(s)
SERIALLY gathering 0 zone(s)
Waiting for concurrent gathering(s) to complete...DONE
******************** Domain: chitzu.ro
11 corrections (cloudflare)
#1: - DELETE chitzu.ro A 158.180.26.241 proxy=false ttl=3600 id=8c08ff55b5f3f00a6fb64ec2789a58a3
#2: - DELETE chitzu.ro MX 10 mail.chitzu.ro. ttl=300 id=57acbe94735b077f1db559bb9bb2afe3
#3: - DELETE chitzu.ro TXT "v=spf1 mx a:mail.chitzu.ro -all" ttl=1 id=4ab36ab9df3cb898dc450a7b6a995a44
#4: - DELETE _dmarc.chitzu.ro TXT "v=DMARC1; p=reject; rua=mailto:dmarc@chitzu.ro; fo=1" ttl=1 id=e30ae8ae75bb1a29914dbc57314d0864
#5: - DELETE mail._domainkey.chitzu.ro TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh8Y/U03AYdCDEOkfHWPYqrH6a/JG9vhOGGFjHGTyIJaPW8lg10URpYdSJs2tVSBP4B/d/tNDocjTJMRABgKksk5OmVBFWSxg3RrV8GRTFkkrl3wMARXGT4LQy7n7K401S+qAYhZvPSww1S9cEmwO5EzNGcycJLEetCze2IqLDBrxjqmr57p8kmaFhdETo5H7B" "uKc2GYUE89YC26f2oz4MV85p9vLQZoXko2i3BdFHHRbFuB+6vbiDzjNLTHsatSww6UqxC6zUNr7y0eAy/x/1ZPGwiT2kQvnjSqSkcq+l8o0iB0AkqEDes6LAlEDY3C+tV3Jptkk2gmbAjZ5SW6VdwIDAQAB" ttl=1 id=d5ef5457dc6d48ee6a8c691e1fc1b98d
#6: - DELETE _minecraft._tcp.chitzu.ro SRV 0 0 25565 mc.chitzu.ro. ttl=1 id=94c5f43b377086c6045270d6f81fcdf5
#7: - DELETE blog.chitzu.ro A 158.180.26.241 proxy=false ttl=1 id=eb8f899f0fc01a8f74350133356f0c2c
#8: - DELETE chat.chitzu.ro A 158.180.26.241 proxy=false ttl=1 id=e24b086ea98a3f74efef4680b05ba1c2
#9: - DELETE gitlab.chitzu.ro A 158.180.26.241 proxy=false ttl=1 id=837f26e08d901c5cf15b1c464cc1aca8
#10: - DELETE mail.chitzu.ro A 89.168.112.28 proxy=false ttl=1 id=18f0de442853a57301e9dcc12660c819
#11: + CREATE test.chitzu.ro A 1.2.3.13 proxy=false ttl=300
Done. 11 corrections.

Deci totul merge ok, mai rămâne doar să convertesc ce am în Cloudflare în JSON pentru dnsconfig.js. După câteva minute bune de troubleshooting și scris de mână, am ajuns la asta:

var REG_NONE = NewRegistrar("none");
var DSP_CLOUDFLARE = NewDnsProvider("cloudflare");

D("chitzu.ro", REG_NONE, DnsProvider(DSP_CLOUDFLARE),
    A("@", "158.180.26.241", TTL(3600)),
    MX("@", 10, "mail", TTL(300)),
    TXT("@", "v=spf1 mx a:mail.chitzu.ro -all", TTL(1)),
    TXT("_dmarc", "v=DMARC1; p=reject; rua=mailto:dmarc@chitzu.ro; fo=1", TTL(1)),
    TXT("mail._domainkey", ["v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh8Y/U03AYdCDEOkfHWPYqrH6a/JG9vhOGGFjHGTyIJaPW8lg10URpYdSJs2tVSBP4B/d/tNDocjTJMRABgKksk5OmVBFWSxg3RrV8GRTFkkrl3wMARXGT4LQy7n7K401S+qAYhZvPSww1S9cEmwO5EzNGcycJLEetCze2IqLDBrxjqmr57p8kmaFhdETo5H7B", "uKc2GYUE89YC26f2oz4MV85p9vLQZoXko2i3BdFHHRbFuB+6vbiDzjNLTHsatSww6UqxC6zUNr7y0eAy/x/1ZPGwiT2kQvnjSqSkcq+l8o0iB0AkqEDes6LAlEDY3C+tV3Jptkk2gmbAjZ5SW6VdwIDAQAB"], TTL(1)),
    SRV("_minecraft._tcp", 0, 0, 25565, "mc.chitzu.ro.", TTL(1)),
    A("blog", "158.180.26.241", TTL(1)),
    A("chat", "158.180.26.241", TTL(1)),
    A("gitlab", "158.180.26.241", TTL(1)),
    A("mail", "89.168.112.28", TTL(1))
);

Am rulat din nou preview și e totul bine:

$ docker run --rm -it -v "$(pwd):/dns"  ghcr.io/stackexchange/dnscontrol preview
CONCURRENTLY gathering 1 zone(s)
SERIALLY gathering 0 zone(s)
Waiting for concurrent gathering(s) to complete...DONE
******************** Domain: chitzu.ro
Done. 0 corrections.

Acum vine partea de CI. În documentație au o parte foarte bine scrisă despre CI/CD pentru Gitlab după care m-am luat și eu, cu mici diferențe. Am ales să nu pun creds.json în repository, în schimb adăugându-l ca variabilă în Gitlab. Inițial voiam să adaug ca file variable, doar că nu permite modul Visibility de "Masked and hidden" din cauza formatului JSON.

Așa că am encodat JSON-ul în base64 și îl voi decoda în pipeline. Urmărind în continuare documentația, au pus un exemplu foarte bun de pipeline pe care l-am folosit și eu cu mici modificări:

.dnscontrol:
  image:
    name: 'stackexchange/dnscontrol'
    entrypoint: ['']
  before_script:
    - '/usr/local/bin/dnscontrol version'
    - 'echo $DNSCONTROL_CREDS | base64 -d > creds.json'

dnscontrol-preview:
  extends: '.dnscontrol'
  stage: 'test'
  script:
    - '/usr/local/bin/dnscontrol check'
    - '/usr/local/bin/dnscontrol preview'
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - 'dnsconfig.js'

dnscontrol-push:
  extends: '.dnscontrol'
  stage: 'deploy'
  script:
    - '/usr/local/bin/dnscontrol push'
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

Ce am făcut diferit e să creez fișierul creds.json și să nu folosesc && $CI_PIPELINE_SOURCE == "web", pentru că dacă trece PR-ul și ceva nu e bine, pot oricând să dau revert. Am dat push (direct în main, încă nu e protected, voiam să testez întâi) și a mers cum mă așteptam.

Acum ar trebui să fie mult mai simplu de editat, urmărit și reparat DNS-ul din Cloudflare.