dns: nsupdate host records to the authoritative server #475

Merged
benvin merged 2 commits from benvin/dns-client-nsupdate into develop 2026-07-12 22:23:19 +10:00
Owner

Replaces the exported-resources → puppet DNS master zone-file flow with per-host RFC2136 dynamic updates against the k8s bind-authoritative write endpoint (198.18.200.9). The master no longer manages zone files.

Design

Each node assembles its DNS records into a local concat file; a systemd .path unit watches it and runs dns-update (nsupdate) on change — exactly the watch-a-file model requested.

Changes

  • profiles::dns::updater (new): concat records file + TSIG key file + dns-update script + dns-update.service (oneshot) + dns-update.path (watcher). The script sends only the delta since last run and deletes removed records, grouped per zone.
  • profiles::dns::record: writes a local concat fragment (zone|name|type|ttl|value) instead of exporting @@concat::fragment to the master.
  • profiles::dns::base: includes profiles::dns::updater (all nodes).
  • hiera: profiles::dns::updater server/key_name/algorithm in common.yaml.

Inert until keyed

The updater does nothing until profiles::dns::updater::key_secret (TSIG) is set in eyaml — records are assembled but not applied, so nodes are safe before the key exists.

Prerequisites (k8s side, separate)

  1. The bind-authoritative zones must set dynamicUpdate: true + an updateKeyRef (a client-update BindTSIGKey) so they accept these updates.
  2. The TSIG key must be shared: the operator-generated key value goes into eyaml here (or the planned Vault-sync feature bridges it).

Validated

puppet parser/epp validate, puppet-lint, and a functional test of the generated per-zone nsupdate message (replace + delete-removed).

Replaces the exported-resources → puppet DNS master zone-file flow with per-host RFC2136 dynamic updates against the k8s **bind-authoritative** write endpoint (198.18.200.9). The master no longer manages zone files. ## Design Each node assembles its DNS records into a local concat file; a systemd `.path` unit watches it and runs `dns-update` (nsupdate) on change — exactly the watch-a-file model requested. ## Changes - **profiles::dns::updater** (new): concat records file + TSIG key file + `dns-update` script + `dns-update.service` (oneshot) + `dns-update.path` (watcher). The script sends only the delta since last run and deletes removed records, grouped per zone. - **profiles::dns::record**: writes a local concat fragment (`zone|name|type|ttl|value`) instead of exporting `@@concat::fragment` to the master. - **profiles::dns::base**: includes `profiles::dns::updater` (all nodes). - **hiera**: `profiles::dns::updater` server/key_name/algorithm in common.yaml. ## Inert until keyed The updater does nothing until `profiles::dns::updater::key_secret` (TSIG) is set in eyaml — records are assembled but not applied, so nodes are safe before the key exists. ## Prerequisites (k8s side, separate) 1. The `bind-authoritative` zones must set `dynamicUpdate: true` + an `updateKeyRef` (a client-update BindTSIGKey) so they accept these updates. 2. The TSIG key must be shared: the operator-generated key value goes into eyaml here (or the planned Vault-sync feature bridges it). ## Validated puppet parser/epp validate, puppet-lint, and a functional test of the generated per-zone nsupdate message (replace + delete-removed).
unkinben added 1 commit 2026-07-11 01:02:07 +10:00
Replaces the exported-resources -> puppet DNS master zone-file flow with
per-host RFC2136 dynamic updates against the k8s bind-authoritative write
endpoint (198.18.200.9), so the master no longer manages zone files.

- add profiles::dns::updater: assembles the host's records into a concat
  file and runs nsupdate via a systemd .path unit that watches it; the
  dns-update script sends only the delta and deletes removed records
- switch profiles::dns::record to write local concat fragments
  (zone|name|type|ttl|value) instead of exporting to the master
- include profiles::dns::updater from profiles::dns::base (all nodes)
- inert until profiles::dns::updater::key_secret (TSIG) is set in eyaml
- hiera: updater server/key_name/algorithm in common.yaml
unkinben force-pushed benvin/dns-client-nsupdate from 225bdc6020 to cf72d0c635 2026-07-11 01:02:07 +10:00 Compare
unkinben added 1 commit 2026-07-12 19:08:52 +10:00
dns: dual-write toggles + drift fact
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
e359b72037
Publish records both ways during the k8s cutover, and expose expected vs
deployed records for drift detection.

- profiles::dns::updater + ::record: manage_nsupdate and manage_export
  booleans (both default on); export keeps the legacy master flow, so
  disable it once k8s is authoritative
- dns_records fact: parses the expected records file and digs the
  authoritative server for each, reporting expected / in_sync / drift
  (plus dns_records_insync boolean); updater writes the server address
  to /var/lib/dns-updater/server for the fact
- hiera: manage_export/manage_nsupdate = true (cutover)
unkinben force-pushed benvin/dns-client-nsupdate from cf72d0c635 to e359b72037 2026-07-12 19:08:52 +10:00 Compare
benvin merged commit 88fcb97ad1 into develop 2026-07-12 22:23:19 +10:00
benvin deleted branch benvin/dns-client-nsupdate 2026-07-12 22:23:19 +10:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/puppet-prod#475