# 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](../../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](../../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 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](#bind-configuration-for-tsig-keys) 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](#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](#bind-configuration-for-tsig-keys). 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): ```bind 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](../../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](#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](../../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](#resolved-decisions) documents `godns`, typed builders, AXFR-only transfer, BIND testcontainer, automatic TCP fallback, ASCII-only names, and git setup in implementation.