Feature Specification: Component bind
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f (core/components/hosting-provider/bind/default.nix, core/components/hosting-provider/bind.nix, core/components/hosting-provider/apex-update-policy.nix, core/components/hosting-provider/dnssec-policy.nix, core/components/hosting-provider/operator-zones.sh, core/resources/dns/self-hosted.nix, core/setups/groups/hosting-provider.nix, core/checks/default.nix, core/checks/ci.nix, core/checks/nix-unit-tests.nix), issues #200 and #1374.
User Scenarios & Testing (mandatory)
User Story 1 - Serve the group apex from its own nameservers (Priority: P1)
A hosting provider holds its apex itself. The bind component runs a BIND
named on each DNS node of the hosting-provider group. One node holds
the apex as primary. The other nodes take the zone from it as secondaries.
The octodns-zone effect pushes the per-node records into the primary at
deploy time. The self-hosted provider of the
dns resource points at this node.
Why this priority: without an authoritative server, no group name resolves, and no node of the group finds its peers.
Independent Test: integrations-octodns-bind. It pushes the
group-derived zone into the primary over RFC2136. It checks the transfer to
the second nameserver, a refused transfer to anyone else, and a peer that
resolves group nodes through the primary.
Acceptance Scenarios:
- Given a node with
serveApex = trueandapexUpdatesset, When the publisher key sends an RFC2136 update, Thennamedaccepts any record type under the apex. - Given a node with
mastersset, When the primary sendsNOTIFY, Then the node transfers the zone with the master TSIG key and refuses all transfers itself. - Given a primary with
secondaries, When a host that is not a listed secondary asks for a transfer, Thennamedrefuses it. - Given an in-bailiwick name in
apexNameserverswithout anapexGlueentry, When the setup evaluates, Then an assertion fails.
User Story 2 - Let operator deploys publish app names (Priority: P2)
An operator deploy publishes the address of its app under the provider
apex. The provider must not lose its own names to the operator. The
operatorUpdates option grants a second TSIG key only A and AAAA
records below the apex. It denies the apex and every name in
reservedNames. The operatorZones option adds an RFC 9432 catalog zone.
In this catalog the operator key adds domains of its own.
Why this priority: operator deploys need names, but a wrong grant lets an operator take over provider names.
Independent Test: integrations-publish-records-named and
nix-unit-setups-operator-publish. The NixOS test publishes, repoints and
releases an app name. It also checks that the server refuses the apex, the
provider names, and every type but the address types to the operator key.
It then adds an operator domain to the catalog.
Acceptance Scenarios:
- Given
operatorUpdateswithreservedNames = [ "auth" ], When the operator key updatesauth.<apex>, Thennamedrefuses the update. - Given
operatorUpdates, When the operator key adds aTXTrecord atapp.<apex>, Thennamedrefuses the update. - Given
operatorZoneson a primary, When the operator key adds a catalog member, Thennamed-operator-zonesseeds the zone once withSOAandNS, and the primary and the secondaries serve it. - Given a catalog member above the apex, above an admitted member, or below the catalog, When
named-operator-zonesruns, Then it refuses the member and leaves it in the catalog.
User Story 3 - Sign the apex with a key from the KV store (Priority: P3)
A provider wants a signed apex so that the registry can publish a DS
record. With signApex = true, the primary signs the apex inline with one
CSK. The key comes from the KV store through signingKey. Pooled
secondaries hold no key and serve the signed zone as they receive it
(#1374).
Why this priority: DNSSEC is optional, but a broken key makes the apex fail validation for every resolver.
Independent Test: integrations-dns-key-render,
integrations-apex-key-mint and nix-unit-setups-apex-signing.
Acceptance Scenarios:
- Given
signApex = trueand asigningKey, Whennamed-signing-keyruns, Then it renames the key toK<domain>.+<alg>+<tag>and writes a.statefile with theKSK:andZSK:roles beforebind.servicestarts. - Given
signApex = trueand no key in the KV store, Whennamed-signing-keyruns, Then the unit fails closed and writes no key file. - Given a signed apex, When
named-apex-dsruns, Then it writes<domain>.dsand<domain>.dnskey.jsonin/var/lib/named. - Given
signApex = trueand nosigningKey, When the setup evaluates, Then an assertion fails.
Edge Cases
- A secret file does not arrive in 180 seconds: the key render unit (
named-apex-key,named-operator-key,named-challenge-key,named-secondary-key-<name>,named-master-key) fails. It does not write an empty secret. - Two DNS nodes of the group roster have the same
label: the evaluation fails incore/setups/wiring/apex-group.nix. Without the group wiring, two nodes that keep the defaultnameserverLabel(ns1) both seed themselves asns1, and the zone answers wrong. - A secondary has a higher serial than the seed:
named-apex-seedraises the seed serial above the secondaries beforebind.servicestarts. - The zone is already published:
named-apex-seeddoes not refresh the seed. It refreshes only while the zone is provably unpublished. - A catalog member drops out: its zone file stays on disk.
- Two catalog members overlap: the first to arrive wins, and the other is refused.
- An incumbent secondary has no TSIG surface:
transferByAddress = trueallows the transfer by address. - The backup reads the apex zone over AXFR under the key of the publisher, and does not keep the SOA, the NS records of the apex and the DNSSEC records. If the transfer has no SOA, the backup fails.
- The restore replays each record set of the backup as a dynamic update that replaces the set with the same name and type, thus
namedincreases the serial and the secondaries transfer the zone. The restore does not remove a record set that is not in the backup.
Requirements (mandatory)
Functional Requirements
- FR-001: The component MUST declare
components.bindwithname = "BIND",package = pkgs.bind, and animplementationthat importsbind.nixand requests thespifferesource. - FR-002:
bind.nixMUST put its options underfediversity.dns-masterand serve the apex that thefediversity.apexoption (core/apex-option.nix) names. - FR-003:
namedMUST act as primary whenmastersis null and as secondary whenmastersis set. - FR-004: A secondary MUST set
allow-transfer { none; }, and a primary MUST sendalso-notifyto itssecondaries. - FR-005: The
update-policyMUST come fromapex-update-policy.nix:zonesub ANYfor the publisher key, then a deny for the apex and each reserved name, thenwildcard *.<apex>. A AAAAfor the operator key. - FR-006: The catalog zone MUST grant the operator key only
wildcard *.zones.<catalog>. PTR. - FR-007:
named-operator-zonesMUST refuse malformed names, names that overlap configured zones, and names that overlap admitted members. - FR-008:
named-operator-zonesMUST run afterbind.service, from a timer withOnBootSec=2minandOnUnitInactiveSec=1min, and from a path unit on the catalog. - FR-009:
challengeUpdatesMUST grant its key onlyTXTat each listed challenge zone. - FR-010: The
dnssec-policyMUST come fromdnssec-policy.nix: one CSK,inline-signing, thesigningAlgorithm(defaultecdsap256sha256) andsignatureValidityDays(default 7). - FR-011:
named-signing-keyMUST run beforebind.service, fail closed, and startbind.servicewith--no-blockafter it succeeds. - FR-012: Each key render unit MUST run after
generate-vars.serviceand fail after 180 seconds without the secret. - FR-013:
systemd.pathsunits MUST re-run the key renders when the secret files, the signing key or the catalog change. - FR-014: The firewall MUST open TCP and UDP port 53.
namedMUST NOT act as an open resolver. - FR-015: Evaluation MUST fail when
mastersis set withoutserveApex, or together withapexUpdatesorsigningKey. - FR-016: Evaluation MUST fail when
operatorUpdatesis set withoutapexUpdates, oroperatorZoneson a primary lacks either update key. - FR-017: Evaluation MUST fail when two lanes name the same zone or two capabilities name the same TSIG key.
- FR-018: The build MUST run
named-checkconf -zonnamed.conf. The check replays thesystemd.tmpfilesrules of/var/lib/namedinto a scratch directory and writes akeyblock with a dummy secret to each key file. The installednamed.confkeeps the/var/lib/namedpaths. - FR-019: The
bindcomponent MUST requestgaragethroughmkBackupsGarageRequestwith the bucketbind-backups. On a primary withapexUpdates,bind/backup.nixMUST add astreamingBackupof the records of the apex zone to that bucket with the restic provider, as the usernamed. A secondary MUST NOT have a backup.
Key Entities
- Apex zone: the zone file
<domain>.zonein/var/lib/named, seeded with serial1970010100bynamed-apex-seed. - Publisher key (
apexUpdates): the TSIG key that theoctodns-zoneeffect uses for whole-zone writes. - Operator key (
operatorUpdates): the TSIG key that operator deploys use for address records and catalog members. - Catalog zone (
operatorZones): an RFC 9432 catalog of operator domains, withnameserversfor each member zone. - Challenge zones (
challengeUpdates): single-name zones for ACMETXTchallenges. - Signing key (
signingKey): the CSK atkeyPath,privatePathandstatePath, read from the KV store (seeopenbao). - Seed serial:
.apex-seed-serialin/var/lib/named. - Offsite backup: the restic repository
bind_apexin thebind-backupsbucket, with the records of the apex zone, also the records that the operator deployments add. It does not hold the operator zones, the challenge zones and the DNSSEC keys.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
integrations-octodns-bindpasses: the primary takes the zone, the secondary transfers it, and an unlisted host gets no transfer. - SC-002:
integrations-publish-records-namedpasses: the operator key writes onlyAandAAAAbelow the apex and catalog members, and the publisher key writes anything. - SC-003:
integrations-dns-key-renderandintegrations-apex-key-mintpass. - SC-004:
nix-unit-setups-delegated-zones,nix-unit-setups-apex-signing,nix-unit-setups-group-zone,nix-unit-setups-dns-routing,nix-unit-setups-operator-publishandnix-unit-setups-apex-grouppass.
Assumptions
- The group roster in
core/setups/groups/hosting-provider.nixsetscomponent = "bind"on each DNS node and gives each a distinct label and a real internal address.core/setups/wiring/apex-group.nixfails the evaluation when two DNS nodes have the same label. - The TSIG secrets and the signing key reach the node as files, through the vars plumbing and the KV store (see
spireandopenbao). - The setups decide which nodes point their resolver at
named, through thenameserversof thenetworkresource (see setups and wiring). - The deployer reaches the primary on port 53 to push records.
Known Deviations
None.