Feature Specification: Effects registrar-*
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/effects/tf/ and core/setups/ at 14504f80d, issue #2131.
The registrar effects publish the delegation of the group apex at the registrar. There is one effect per step: the domain registration, the nameservers, the DNSSEC key, and one glue effect per DNS node label. Each effect applies one OpenTofu graph with one provider and one resource. The records in the zone are the job of octodns.md. The registrar holds no records of the zone, so that only the delegation depends on the choice of registrar, and the zones keep the standard protocols of the DNS services of the deployment. Where deSEC holds the apex, the effects delegate it to deSEC instead: no glue, the NS set of deSEC, and the key that deSEC signs with. The effects deploy no nodes. The tf-incus setup builds them (tf-incus.md). The shared behavior of a standalone effect is in spec.md. The verbs and the state rules are in 004.
One dispatch table names, per registrar, the steps that it offers, the effect of each step and the coordinates that each step needs:
| Registrar | domain | nameservers | glue | dnssec |
|---|---|---|---|---|
porkbun |
yes (max cost) | yes | yes | yes (DS) |
inwx |
no | no | yes | yes (DNSKEY) |
openprovider |
yes (max cost, owner handle) | nameserver group | yes | no (keys on the registration) |
User Scenarios & Testing (mandatory)
User Story 1 - Delegate the apex to the group nameservers (Priority: P1)
An operator deploys a group that runs its own nameservers. The operator applies the glue for each DNS node and then the nameservers. The parent zone then sends resolvers to the group nameservers.
Why this priority: Without the delegation, no resolver finds the zone that octodns-zone publishes.
Independent Test: A unit test of the setup checks the arguments and the state keys of each step, and a unit test evaluates each generated graph. No integration test applies the glue or the nameservers.
Acceptance Scenarios:
- Given a registrar, a registrar state address and a group with DNS nodes, When the catalog lists the group effects, Then it lists one glue effect for each DNS node label of that group.
- Given a nameserver in the zone that it serves, When the operator applies the nameservers at
openprovider, Then the nameserver group carries the IPv4 and IPv6 addresses of the node. - Given a DNS node that this deployment does not stand up, When the setup evaluates, Then it makes no glue for that node.
User Story 2 - Register the apex under a spending bound (Priority: P1)
The operator states a maximum cost and applies the domain registration. The provider asks the registrar for a live quote and registers the domain only at or below the bound.
Why this priority: A registration spends money and holds the name that all other records depend on.
Independent Test: A unit test of the setup checks that the registration is absent until its coordinates are given. An integration test applies the porkbun and openprovider registrations against a fake registrar API: a quote above the bound fails the apply and orders nothing, a quote at the bound registers, and a destroy plan fails. The build of the openprovider provider runs its unit tests, which also refuse a quote in a different currency, a zero quote and a failed quote.
Acceptance Scenarios:
- Given no maximum cost, When the catalog lists the group effects, Then it does not list the domain registration.
- Given a quote above the bound, in a different currency, or no quote, When the operator applies the registration, Then the apply fails and names the numbers.
- Given a registered domain, When the operator runs a destroy, Then OpenTofu refuses.
User Story 3 - Close the chain of trust (Priority: P2)
The apex node makes its signing key at run time and writes its DS to a file. The operator copies that value into the DS or DNSKEY coordinate and applies the DNSSEC step.
Why this priority: The zone is signed, but validating resolvers trust it only when the parent holds the DS.
Independent Test: A unit test of the setup checks that the DS reads the same as four fields or as a whole record.
Acceptance Scenarios:
- Given no
DScoordinate, When the catalog lists the group effects, Then it does not list the DNSSEC step. - Given a whole
DSrecord, When the setup evaluates, Then the step takes the key tag, the algorithm, the digest type and the digest from it. - Given a
DSvalue that does not parse, When the setup evaluates, Then the evaluation fails and prints the value.
User Story 4 - Delegate the apex to deSEC (Priority: P2)
The group DNS is desec, so deSEC holds the apex and signs it. The operator applies the nameservers and the DNSSEC step (at openprovider: the registration). The parent zone then sends resolvers to deSEC and holds the DS of the key that deSEC signs with. The operator states no key: the plan reads it from the deSEC API.
Why this priority: The apex then needs no nameserver of the group, but a delegation that points elsewhere, or a DS of a different key, makes the domain fail to resolve.
Independent Test: A unit test of the setup checks the NS set, the absent glue, and the key read of each registrar. A unit test evaluates each generated graph with the key read. The check deployment-zone-keys-probe runs the key read against a fake deSEC API.
Acceptance Scenarios:
- Given a
desecgroup DNS and no stated apexNSset, When the setup evaluates, Then the nameserver step namesns1.desec.ioandns2.desec.org. - Given a
desecgroup DNS and a stated apexNSset, When the setup evaluates, Then the nameserver step names the stated set. - Given a
desecgroup DNS and noDSorDNSKEYcoordinate, When the operator plans the DNSSEC step, Then the plan reads the key-signing key of the apex from deSEC with the deSEC token of the deploy, and the step publishes it. - Given a
desecgroup DNS and aDSorDNSKEYcoordinate, When the setup evaluates, Then the step uses the stated key and reads nothing from deSEC.
Edge Cases
- A registrar that the table does not hold: the evaluation fails and lists the known registrars.
- No registrar state address: no registrar effect exists. There is no local state for them.
- External group DNS other than
desec: no registrar effect exists, because that apex is not ours to delegate. desecgroup DNS: no glue effect exists, because the nameservers of deSEC are not in the zone.- A step that the registrar does not offer: that effect does not exist.
- A
DSdigest on more than one line: the setup joins the parts. openprovideraccepts no dots in the name of a nameserver group: the setup makes the name from the domain without dots.- A teardown at
openprovidercan delete the nameserver group, so a later bootstrap can create it again.
Requirements (mandatory)
Functional Requirements
- FR-001: The catalog MUST offer a registrar effect only when a registrar and a registrar state address are given, and either the group DNS is not external and the group has DNS nodes, or the group DNS is
desec. - FR-002: The catalog MUST offer the domain, nameserver and DNSSEC steps only when the registrar offers that step and every coordinate that the step needs is given. Under
desecgroup DNS, theDSand theDNSKEYcount as given. - FR-003: The catalog MUST offer the glue effect of a label only for groups that run a DNS node with that label, and only when the group DNS is not external.
- FR-004: Each effect MUST read the key set of the
tf-incusmethod plus the registrar keys: the registrar, the maximum cost, theDS, theDNSKEY, the owner handle and the state address. - FR-005: Each effect MUST offer the OpenTofu verbs, and its run MUST be the apply.
- FR-006: Each step MUST keep its state in the HTTP backend at the registrar state address plus a suffix of its own.
- FR-007: OpenTofu MUST refuse to destroy the registration.
- FR-008: The registration MUST refuse to apply when the live quote is above the maximum cost (in US cents), is in a different currency, or is missing.
- FR-009: The glue of a node MUST name the node and hold all its IPv4 and IPv6 addresses.
- FR-010: The nameserver step MUST name the delegated
NSset as hostnames, and MUST be absent when that set is empty. The delegated set is the apexNSset of the group. Underdesecgroup DNS, it is the stated apexNSset, orns1.desec.ioandns2.desec.orgwhen none is stated. - FR-011: The setup MUST accept a
DSas four fields or as a whole record, and aDNSKEYas four fields, as a whole record or as JSON. It MUST fail the evaluation on any other form. - FR-012: At
openprovider, the registration MUST set the nameserver group only when the stated apexNSset is not empty or the group DNS isdesec, and the DNSSEC keys only when aDNSKEYis given or read from deSEC. - FR-013: Each provider graph MUST use a committed provider schema, a single provider, and the exact provider version.
- FR-014: Each provider graph MUST expose only the resource types that the registrar steps use, and MUST NOT expose zone records, contacts, customers or certificate orders.
- FR-015: Under
desecgroup DNS, the DNSSEC step MUST read the key-signing key of the apex from the deSEC API at plan time when noDSorDNSKEYis given, with the token file thatDNS_API_TOKEN_FILEnames. ADSread takes the SHA-256 digest. A statedDSorDNSKEYMUST win over the read.
Key Entities
- Registrar: The account at which the group apex is registered:
porkbun,inwxoropenprovider. - Step: The domain, the nameservers, the glue or the DNSSEC key, each with one effect and its own state.
- Dispatch table: The table of steps per registrar. The catalog reads it for availability, and the setup for the steps.
- Apex
DS/DNSKEY: The public key data that the apex node makes, which the operator gives back to the effect as a coordinate. Underdesecgroup DNS, the plan reads it from deSEC.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: After all applies, a query for
NSat the parent zone gives the apexNSset of the group. - SC-002: After the DNSSEC step, the parent zone serves the
DSof the key that signs the apex: the key that the apex node wrote, or the key of deSEC. - SC-003: No registration costs more than the maximum cost.
- SC-004: A second plan of each step after its apply shows no change.
Assumptions
- The operator applies the steps in order. At
openprovider: register without a nameserver group, then the glue for each nameserver, then the nameserver group, then the registration with the nameserver group, then the registration with the DNSSEC keys. Atporkbun: the glue before the delegation. Underdesecgroup DNS atopenprovider: the nameserver group, then the registration. - A move of a signed apex from the group nameservers to deSEC goes through an unsigned delegation. The registrar steps give the
openproviderprovider no empty list of DNSSEC keys, and the provider omits an empty nameserver group, thus a step without them changes nothing. The operator removes theDSat the registrar by hand. Seenotes/cd-credentials.md. - The addresses of the DNS nodes are pinned in NetBox, so the glue changes only on a full rebootstrap.
- The cost bound and the
openproviderglue resource come from pinned providers. Theopenproviderpin is upstreammainuntil a release contains the changes the registrar step needs. - The registrar credentials reach the provider through the environment of the machine that runs the apply.
Known Deviations
- The effect registry defines a domain effect for
inwx, but the dispatch table givesinwxno domain step, so no registrar effect reaches it (#2131).