Commit Graph
2 Commits
Author SHA1 Message Date
57_WolveandClaude Opus 5 0c151ce79b feat(knot-dns): add the missing host-level installer
The deployment shipped its payload but not the thing that installs it.
54a5c09 added README, knot.conf, knsctl, zone.tmpl, secrets.conf.example,
the aliases and the MOTD -- but no deploy.sh, no cloud-init.yml, and no
entry in automations.sh's DEPLOYMENTS. `git log --all` confirms deploy.sh
was never committed and it is not gitignored, yet README.md:96 and :100
tell the operator to run it. So the documented install path did not exist.

Alpine only, native, matching the README: Knot binds :53 directly, needs
real client addresses for RRL and cookies, and keeps its DNSSEC key store on
the host filesystem. The RHEL packaging needs EPEL, which nothing here sets
up, so anything that is not Alpine dies with a clear message rather than
half-installing somewhere untested.

Three decisions worth recording:

The include chain is stubbed. knot.conf include:s seven files the `dns` repo
owns; Knot treats a missing include as a config error, so a node the pipeline
has never delivered to would fail conf-check and never start. deploy.sh
writes a placeholder for each one that is ABSENT -- never over a delivered
file -- so the node comes up healthy serving no zones until the pipeline
lands.

TSIG is generated on a primary and required on a secondary. The keys must
match byte for byte, so a secondary that generated its own would
authenticate nothing; it now refuses to deploy without TSIG_AUTHORITIVE and
TSIG_ADMIN. A primary generates both and prints them once. An existing
secrets.conf is never rewritten, so a re-run cannot rotate a key out from
under a running estate.

PRIMARY_ADDR seeds a minimal remotes.conf on a secondary so it can bootstrap
by AXFR before the pipeline runs -- written only when remotes.conf was
absent, verified by re-running against a delivered file and confirming it is
left untouched.

Re-runs apply changes rather than freezing at first deploy, per the pattern
this repo just adopted elsewhere: knot.conf is re-rendered from .env every
run, env-presence is captured before the ":=" defaults, and values passed to
a re-run are written back to .env with the awk-based set_env from 947c899 --
which matters here because a TSIG secret can contain the characters that
broke the sed-based one.

Two bugs caught while testing this, before it shipped:
- the secrets.conf renderer used `++n` as a gsub argument, which awk
  evaluates on every line, not just matching ones -- both keys would have
  received the SAME secret, making the read-only admin key identical to the
  replication key. Increments on a matching line only now.
- the MOTD is a pre-drawn box, so substituting values of a different width
  than their @TOKEN@ shifted the right border on every login. Values are now
  padded to the token's span, measured over an ASCII-only region so it holds
  under busybox awk in the C locale; an over-long value overflows rather than
  being truncated.

Verified: knot.conf renders identity/NSID/listen and leaves the control
socket alone; all seven stubs are created on a fresh node and skipped on a
re-run; the remotes.conf seed fires only for a fresh secondary; .env seeds
every runtime key; the MOTD renders with no leftover tokens and an aligned
border. Not verified: apk, knotc and the service start, which need an actual
Alpine host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:32:45 -05:00
57_WolveandClaude Opus 5 54a5c0931a feat(knot-dns): authoritative Knot DNS node deployment
Native Alpine deployment for the anycast DNS estate -- no Docker, no Caddy,
alongside squid and openbao as an exception to the repo norm. Knot binds :53
directly, needs real client addresses for RRL and DNS cookies, and its DNSSEC
key store must live on the host filesystem.

Deploys a NODE. Zone data lives in the separate dns repo and arrives from its
pipeline. The split is /etc/knot/knot.conf: written here once as a skeleton of
include: lines covering only what belongs to a box (identity, NSID, storage
paths, listen, logging, control socket); everything that is DNS policy --
templates, dnssec policy, remotes/ACLs, modules, the domain inventory and the
zone files -- is delivered by the dns repo.

knsctl replaces adddns.pl and adddnssec.pl, fixing four defects:
- the duplicate check searched for the domain in BIND named.conf double-quote
  syntax (/"$domain"/) against unquoted YAML, so it could never match; only
  the -f zone-file test ever caught anything
- neither script consulted the other class's manifest, so a domain already in
  public.conf could be appended to dnssec.conf and fail the reload AFTER both
  files had been written
- nothing validated before reloading
- the reload was non-blocking, so a rejected config reported success

Its manifest matching is anchored on the YAML key and escapes the dot, so
barsrvno.de and srvnoXde no longer false-positive against srvno.de.

Aliases preserve the existing muscle memory with three corrections: -b on
every triggering knotc command (without it knotc returns OK when the command
was SENT, not when it succeeded); knzr (zone-reload) added alongside knrl
(reload), since reloading one zone's data is the right verb for a record
change and a full reload is only needed when a zone is added or removed; and
serial/NSID helpers that query unicast addresses, because asking the anycast
service address reaches whichever node is nearest and says nothing about
which node is stale.

Break-glass writes (add/remove/edit) warn and audit-log: they are overwritten
by the next pipeline deploy unless the change also lands in git. Removal
refuses to purge DNSSEC keys -- zone-purge +keys is irreversible on Knot
3.5.x, the key trash bin having arrived in 3.6.0 -- and prints the ordering
requirement, since removing a signed zone before the parent DS is withdrawn
is an outage for validating resolvers rather than a graceful shutdown.

deploy.sh, build.sh and cloud-init.yml are deliberately not included yet;
they are blocked on the Knot version decision, which sets the apk pin and
feature availability. See the Status section in the README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 00:36:38 -05:00