Files
go-dns/docs/changes/00001-setup-project/spec.md
T

20 KiB
Raw Blame History

Initial project setup and DNS operation catalog

Goal

Bootstrap go-dns as a new Go module in the workspace and define the operation surface the library will support:

  • Dynamic DNS updates against BIND-style authoritative nameservers (RFC 2136, nsupdate semantics)
  • Zone transfer — full zone read via AXFR only (IXFR deferred — see backlog.md)
  • TSIG key authentication for all of the above (callers supply existing keys; key generation is out of scope)
  • Zone data structures to parse transfer/update responses and hold complete zone contents in memory
  • BIND configuration guide describing how a TSIG key must be configured on the server for updates and zone transfers

This change establishes project identity, documentation layout, and an agreed catalog of supported operations and data models. It does not implement the library API or wire protocol yet.

Background

Authoritative DNS on BIND is commonly manipulated in two ways over the wire:

  1. Dynamic update — add, change, or remove resource records without editing zone files (nsupdate, RFC 2136).
  2. Zone transfer — read the full zone via AXFR (RFC 5936). Incremental transfer (IXFR) is out of scope for the initial library; see backlog.md.

Both paths are authenticated on production servers with TSIG shared secrets (RFC 2845, RFC 8945): the client signs requests; BIND verifies the signature and applies ACLs (allow-update, allow-transfer) for that key.

go-dns will be an importable Go library so other projects (automation, controllers, internal tools) can perform updates and zone transfers programmatically — with typed data structures, validation, and testability — instead of shelling out to nsupdate or dig axfr.

Sibling workspace projects (go-ollama, etc.) follow: module path gitea.neitzel.de/konrad/<project>, project spec under docs/, and .cursor/rules/project-specification.mdc.

DNS operations — supported interface

The following catalog describes what the library interface must be able to express. How (exact Go type names, function signatures, error shapes) is deferred to later changes. Wording follows RFC 2136, RFC 5936, and common BIND 9 / nsupdate usage.

1. Session, authentication, and transport

Operations that establish where, how, and as whom requests are sent.

Operation Description
Set server Target authoritative nameserver (hostname or IP, optional port; default 53).
Set zone Origin zone name (UPDATE question section; AXFR QNAME).
Configure TSIG key Authenticate with an existing shared secret: key name, algorithm (e.g. hmac-sha256), and secret (Base64). Callers provide keys; the library does not generate keys.
Choose transport UDP first for UPDATE where applicable; automatic TCP fallback on truncation (TC bit) or oversize; AXFR always uses TCP.
Timeout / context Caller-controlled deadline and cancellation (maps to Go context.Context).

Authentication model:

  • TSIG is mandatory for the documented happy path (updates and zone transfers).
  • The same key may be used for both UPDATE and AXFR when BIND ACLs permit (see BIND configuration below).
  • Out of scope: key generation (tsig-keygen, rndc tsig-keygen, etc.), GSS-TSIG, SIG(0), unauthenticated UPDATE or AXFR.

2. Update actions (zone mutations)

Operations that modify RRsets in the zone — the Update section of RFC 2136.

Operation Description nsupdate analogue
Add RR Insert one or more RRs at a name. If the RRset exists, new RRs are merged in; duplicates may be rejected or ignored per server policy. add name [ttl] [class] type rdata
Delete RR (exact) Remove RRs matching name, class, type, and exact rdata. Other RRs at the same name/type remain. delete name [ttl] [class] type rdata
Delete RRset Remove all RRs with the given name, class, and type (rdata wildcard). delete name [ttl] [class] type
Delete all at name Remove all RRs at a name (any type), optionally scoped by class. delete name / update delete name
Replace RRset Convenience composition: delete an RRset (or exact match) then add new RRs — same net effect as delete-then-add in one UPDATE. two-step delete + add in one send
Set TTL on add Specify TTL when adding; omit to use server default where applicable. TTL field on add
Set class Default IN (Internet); allow other classes only if explicitly required later. [class] on add/delete

Each row may appear multiple times in a single UPDATE message before submission.

3. Prerequisites (conditional updates)

Operations in the Prerequisite section — the update fails entirely if any prerequisite is not met (no partial apply).

Operation Description nsupdate analogue
Name in use The name must already have at least one RR (any type). prereq nxdomain inverse / requires patterns
Name not in use The name must have no RRs (NXDOMAIN or empty owner). prereq nxdomain
RRset exists An RRset with given name, class, and type must exist. Optional: full set of rdata values must match. prereq yxrrset
RRset not exists No RRset for name/class/type, or no RRs matching given rdata set. prereq nxrrset
RR exists A specific RR (name, class, type, rdata) must exist. prereq yxrrset with rdata
RR not exists That specific RR must not exist. prereq nxrrset with rdata

Prerequisites are optional but recommended for idempotent automation (compare-and-swap style updates).

4. Resource record types and typed builders

The interface MUST use typed builders per RR type — not a generic (type, rdata string) API. Each supported type has a dedicated Go type (or builder) with validated fields (e.g. ARecord with net.IP, MXRecord with priority + host, SRVRecord with priority/weight/port/target).

Initial target set:

Category Types
Address A, AAAA
Names CNAME, PTR, NS
Text / metadata TXT, CAA
Mail / service MX, SRV
DNSSEC-related (dynamic) CDS, CDNSKEY (where policy allows)
Other HINFO, NAPTR, TLSA, SSHFP

For RR types returned in AXFR but not yet given a typed builder, Record may hold a typed unknown wrapper (type number + validated wire rdata bytes or presentation parse) until a builder is added — callers must not rely on unparsed strings for updates.

Per-type field validation (e.g. hostname vs IP) is implemented with the typed builders in a later change; this catalog requires typed expression only, not free-form rdata strings.

SOA serial manipulation is not a direct client operation; BIND increments the SOA serial automatically on successful updates.

5. Update submission and batching

Operation Description
Build update message Collect prerequisites and update actions into one RFC 2136 UPDATE.
Send update Transmit the TSIG-signed message and receive the response.
Batch multiple changes One send applies many add/delete/prerequisite operations atomically (all succeed or none).
Interpret update response Map server RCODE to success or typed errors (e.g. REFUSED, NOTAUTH, NXDOMAIN, YXDOMAIN, NOTZONE, BADSIG).

There is no “show” or local-only preview in the protocol; local validation before send is a library concern, not a server operation.

6. Zone transfer (AXFR)

Operations to read the full zone from the authoritative server (distinct from RFC 2136 UPDATE).

Operation Description Typical use
AXFR (full transfer) Download all RRs in the zone in one TSIG-signed transfer session over TCP. Sync, backup, inspect zone contents
Parse transfer stream Decode multi-message AXFR response into structured zone data (see Zone data model). Populate in-memory zone
Interpret transfer errors REFUSED (ACL), NOTAUTH (TSIG/ACL), BADSIG, FORMERR, etc. Caller error handling

Constraints:

  • AXFR always uses TCP.
  • Transfers require allow-transfer (or equivalent policy) for the TSIG key on the server — see BIND configuration.

Record types in transferred zones include SOA, NS, and all RR types present in the zone — not limited to the update-target set in section 4.

7. Zone data model

The library MUST define concrete data types (implemented in Go in a later change) to hold parsed zone contents and operation results. Logical model:

Record (single RR)

Field Description
Owner Domain name (FQDN, normalized)
Class DNS class (default IN)
Type RR type (A, AAAA, SOA, … or generic numeric type)
TTL Time to live in seconds
RData Type-specific value matching the RR type (typed builder / sum type — not a raw presentation string)

RRSet (optional grouping)

Field Description
Owner, Class, Type RRset key
Records One or more Record values sharing owner/class/type (multiple RData for same RRset)
TTL RRset TTL (consistent across members in a well-formed zone)

Zone

Field Description
Origin Zone apex name (e.g. example.com.)
Records All RRs from transfer or constructed locally — complete zone contents
SOA SOA RR extracted for convenience (serial, refresh, retry, expire, minimum TTL, MNAME, RNAME)
Serial SOA serial number (from SOA RR after transfer)

Access patterns the types must support (behavioral requirements, not API names):

  • Iterate all records; lookup by owner name; lookup by owner + type.
  • After AXFR, populate Zone with the full current zone.
  • After UPDATE, optionally reflect mutations in an in-memory Zone if the caller maintains one (library may offer helpers).
  • Export to a stable sorted text representation (zone-file-like) for debugging/tests (exact format in plan).

Transfer / update result wrappers

Type (logical) Holds
UpdateResult Success/failure, RCODE, optional server message
TransferResult Resulting Zone, transfer kind (AXFR), ending serial (SOA after transfer)

8. Read-only / auxiliary (library-side)

Operation Description
Validate rdata Client-side syntax check before send (optional helper).
Normalize name FQDN vs relative-to-zone, trailing dot; ASCII hostnames only in v1 (reject non-ASCII / Unicode labels; IDNA/punycode deferred).
Dry run / encode only Produce wire-format or human-readable message without sending (testing and debugging).
Zone diff Compare two Zone values (optional helper for automation; detail in plan).

Out of scope: recursive resolver lookups (A/AAAA for unrelated names), catalog zones, BIND rndc control channel.

9. BIND configuration for TSIG keys

The project MUST ship a BIND configuration guide (English) describing how to configure BIND 9 so a single TSIG key can perform dynamic updates and full zone transfers (AXFR). Target location: docs/bind-tsig-configuration.md (created in implementation plan; content requirements defined here).

The guide MUST cover at minimum:

  1. Define the key — key "name" { algorithm ...; secret "..."; }; in named.conf or an included file. State explicitly that key generation is out of library scope; operators use tsig-keygen or rndc tsig-keygen (or copy from existing secret management).
  2. Reference the key in the zone — for the target zone "example.com" { ... } block:
    • allow-update { key "name"; }; — permits RFC 2136 UPDATE signed with that key.
    • allow-transfer { key "name"; }; — permits AXFR to clients signing with that key.
  3. Optional stricter policy — mention update-policy (grant rules per name/type) as an alternative or supplement to broad allow-update.
  4. Reload / validation — named-checkconf, rndc reload, and verifying with nsupdate -y and dig -y / TSIG-signed AXFR.
  5. Security notes — restrict key to needed zones; do not expose AXFR to the public Internet; separate keys for read vs write if policy requires (library supports different keys per client instance even if the guide shows one key for simplicity).
  6. Minimal complete example — one named.conf fragment: key + zone with allow-update and allow-transfer for the same key.

Example fragment (illustrative; the guide expands this):

key "go-dns-key" {
    algorithm hmac-sha256;
    secret "base64secret==";
};

zone "example.com" {
    type master;
    file "/var/lib/bind/db.example.com";
    allow-update { key "go-dns-key"; };
    allow-transfer { key "go-dns-key"; };
};

10. Explicit non-goals (initial version)

Excluded Reason
TSIG key generation Operator/tooling concern (tsig-keygen, secret store)
DNSSEC inline signing workflow Complex policy; may revisit later
Unauthenticated UPDATE or AXFR Security risk; TSIG is the default path
subprocess wrapper around nsupdate / dig Library implements the protocol directly
Non-BIND servers Best-effort compatibility possible, but BIND 9 is the reference
IDNA / Unicode domain names ASCII-only in v1; punycode support deferred
IXFR (incremental zone transfer) Full AXFR only in v1; see backlog.md

Scope

  • Register the project in workspace go.work
  • Initialize Go module gitea.neitzel.de/konrad/go-dns
  • Add project Cursor rule and docs/project-specification.md (summary + pointer to this catalog)
  • Add README.md, .gitignore, docs/backlog.md, and docs/changes/ layout
  • Document the full DNS operation catalog (update, transfer, data model, BIND guide requirements) as the agreed interface surface
  • Ensure go work sync succeeds with the new module (no application code required in this phase)

Out of scope

  • Go API implementation (types, interfaces, function bodies)
  • RFC 2136 / AXFR wire encoding and TSIG signing implementation
  • Unit or integration tests for update or transfer logic
  • CLI binary, HTTP service, or Docker deployment (production compose — not required for library; BIND testcontainer is used for integration tests only)
  • git init, Gitea remote, and first push in this specify phase — deferred to implementation plan (see Resolved decisions)
  • Detailed per-record-type rdata validator implementation (typed builder shapes are decided here; code in a later change)
  • TSIG key generation — operators create keys with BIND tooling or secret management; library accepts key name, algorithm, secret only
  • Writing docs/bind-tsig-configuration.md in this phase (requirements for it are in this spec; file is created during implementation)

Requirements

  1. Module path MUST be gitea.neitzel.de/konrad/go-dns per workspace infrastructure rules.
  2. Go version in go.mod MUST match workspace go.work (currently 1.26.4).
  3. Project MUST be listed in root go.work under use (...).
  4. .cursor/rules/project-specification.mdc MUST exist with alwaysApply: true and reference docs/project-specification.md.
  5. docs/project-specification.md MUST summarize purpose, BIND target, RFC 2136 + AXFR, TSIG authentication, zone data model, and point to this operation catalog.
  6. This spec.md MUST contain the full operation catalog (sections 1–10): session/auth, updates, prerequisites, RR types, update submission, AXFR, zone data model, auxiliary helpers, BIND configuration guide requirements, and non-goals.
  7. This spec.md MUST require TSIG key authentication for updates and transfers; key generation MUST be listed as out of scope.
  8. This spec.md MUST define logical types Record, RRSet, Zone, UpdateResult, and TransferResult for holding zone data and responses.
  9. README.md MUST be in English and describe the project intent (updates + zone transfer) and link to this spec.
  10. .gitignore MUST exclude common Go artifacts and local secret config paths.
  11. docs/changes/ MUST exist for future change efforts.
  12. Public Go package name MUST be godns (import gitea.neitzel.de/konrad/go-dns, package godns at module root).
  13. RR construction MUST use typed builders per type; a generic rdata string API MUST NOT be the primary interface.
  14. Integration tests (future change) MUST use a BIND testcontainer via testcontainers-go; tag integration per workspace rules.
  15. UPDATE transport MUST automatically fall back to TCP when UDP response is truncated or unsuitable.
  16. Domain names MUST be ASCII-only in v1; reject Unicode labels at validation time.
  17. docs/backlog.md MUST exist with IXFR support listed as a future backlog item.

Resolved decisions

Question Decision
Root package name godns — package godns at module root
RData API Typed builders per RR type (A, AAAA, MX, …); no generic rdata string as primary API
Zone transfer AXFR only in v1; IXFR tracked in backlog.md
Integration testing BIND testcontainer (testcontainers-go); go test -tags=integration ./...
TCP fallback Automatic on UDP truncation / oversize for UPDATE
IDNA / Unicode ASCII only initially; IDNA deferred
Git repository git init + Gitea remote during implementation of this change (plan phase), not in specify phase

Remote URL per workspace infrastructure: ssh://git@gitea.neitzel.de:2222/konrad/go-dns.git

Constraints

  • Follow workspace change-driven development: library implementation happens only after this spec and an approved plan.md.
  • Documentation and comments in English.
  • Library-first design (importable package), not a mandatory daemon or CLI.
  • TSIG is the primary authentication mechanism; do not design for unsigned updates or transfers as the happy path.
  • Zone transfers use TCP; UPDATE uses UDP with mandatory automatic TCP fallback on truncation.
  • Domain names: ASCII-only validation in v1.
  • Integration tests: BIND testcontainer only (no optional external nameserver config required for CI).
  • Match conventions of existing workspace Go libraries (e.g. go-ollama) where applicable.

Acceptance criteria

  1. AC-1: grep -q go-dns /Users/konrad/Projects/go/go.work and go work sync from the workspace root completes without error.
  2. AC-2: go-dns/go.mod declares module gitea.neitzel.de/konrad/go-dns and go 1.26.4.
  3. AC-3: go-dns/.cursor/rules/project-specification.mdc exists, has alwaysApply: true, and references docs/project-specification.md.
  4. AC-4: go-dns/docs/project-specification.md documents module path, BIND target, RFC 2136 + AXFR, TSIG auth, zone data model, and references this spec.
  5. AC-5: go-dns/README.md exists, is in English, mentions updates and AXFR zone transfer, and links to this spec.
  6. AC-6: This spec.md includes sections 1–10: updates, AXFR (no IXFR), zone data model (Record, RRSet, Zone, result types), BIND configuration guide requirements, and non-goals including no key generation and no IXFR.
  7. AC-7: This spec.md specifies that docs/bind-tsig-configuration.md will document allow-update and allow-transfer for a TSIG key with a minimal named.conf example.
  8. AC-8: go-dns/docs/backlog.md exists with an IXFR backlog entry.
  9. AC-9: go-dns/docs/changes/00001-setup-project/ contains this spec.md (and later plan.md, verification.md, changes.md per workflow).
  10. AC-10: Resolved decisions documents godns, typed builders, AXFR-only transfer, BIND testcontainer, automatic TCP fallback, ASCII-only names, and git setup in implementation.