chore: baseline before change C-00001-setup-project

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-07-04 14:26:47 +02:00
co-authored by Cursor
commit 47f17445d0
8 changed files with 726 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
# 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.