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

306 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>`, 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.