commit 47f17445d0e2e46d9b8d7f2d253a3acc2e00db60 Author: Konrad Neitzel Date: Sat Jul 4 14:26:47 2026 +0200 chore: baseline before change C-00001-setup-project Co-authored-by: Cursor diff --git a/.cursor/rules/project-specification.mdc b/.cursor/rules/project-specification.mdc new file mode 100644 index 0000000..c18888f --- /dev/null +++ b/.cursor/rules/project-specification.mdc @@ -0,0 +1,33 @@ +--- +description: go-dns — Go library for dynamic DNS updates (RFC 2136 / nsupdate-style) +alwaysApply: true +--- + +# go-dns — Project Rules + +Follow `docs/project-specification.md` for design and implementation. + +## Stack + +- **Language:** Go (see workspace `go.work` for version) +- **Module:** `gitea.neitzel.de/konrad/go-dns` +- **Package:** `godns` (module root) +- **Type:** Importable Go library (not a standalone service) + +## Design + +- Target **BIND** and RFC 2136–compatible authoritative nameservers +- **Dynamic update** (RFC 2136) and **full zone transfer** (AXFR) over DNS +- **TSIG** authentication with caller-supplied keys (no key generation in library) +- Typed **RR builders** per record type (not generic rdata strings) +- Domain names: **ASCII only** in v1 +- UPDATE: **automatic TCP fallback** on UDP truncation +- Integration tests: **BIND testcontainer** (`go test -tags=integration ./...`) +- **`docs/bind-tsig-configuration.md`** — BIND `allow-update` / `allow-transfer` guide (implementation phase) +- Typed **zone data structures** (`Record`, `Zone`, etc.) for transfer/update results +- Project `.cursor` overrides workspace rules for this module + +## Tests + +- Unit: `go test ./...` +- Integration: `go test -tags=integration ./...` — **BIND testcontainer** (Docker required) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..196ba28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Binaries +/go-dns +/go-dns-bin/ +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test / coverage +*.test +*.out +coverage.html + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store + +# Local config (secrets) +testconfig/local.toml diff --git a/README.md b/README.md new file mode 100644 index 0000000..df04862 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# go-dns + +Go library for authoritative DNS operations against BIND (and compatible) nameservers: + +- **Dynamic updates** — `nsupdate`-style changes (RFC 2136) +- **Zone transfer** — full zone read via **AXFR** (IXFR on [backlog](docs/backlog.md)) +- **TSIG key authentication** (callers provide keys; generation out of scope) + +## Status + +Early project setup. The supported operation surface, zone data model, and BIND configuration requirements are defined in [docs/changes/00001-setup-project/spec.md](docs/changes/00001-setup-project/spec.md). Implementation follows the change-driven workflow (spec → plan → implement). + +## Module + +```bash +go get gitea.neitzel.de/konrad/go-dns +``` + +Module path: `gitea.neitzel.de/konrad/go-dns` — import as **`godns`**. + +## Documentation + +- [Project specification](docs/project-specification.md) +- [Product backlog](docs/backlog.md) +- [Change efforts](docs/changes/) diff --git a/docs/backlog.md b/docs/backlog.md new file mode 100644 index 0000000..57e0297 --- /dev/null +++ b/docs/backlog.md @@ -0,0 +1,40 @@ +# go-dns — Product Backlog + +Potential features for future change efforts. **Not a commitment** — items move into `docs/changes/` when scoped and prioritized. + +Align with [project-specification.md](project-specification.md): **`godns`** package, TSIG auth, typed RR builders, ASCII-only names, BIND testcontainer integration tests. + +**Priority legend:** P0 = core library, P1 = soon after core, P2 = enhancement, P3 = later / optional + +--- + +## Zone transfer + +| Priority | Item | +|----------|------| +| P2 | **IXFR (incremental zone transfer)** — RFC 1995; apply deltas to a prior `Zone` snapshot; SOA serial baseline; fallback to AXFR when IXFR unavailable; extend `TransferResult` and BIND guide | + +--- + +## Internationalization + +| Priority | Item | +|----------|------| +| P3 | **IDNA / punycode** — Unicode domain labels; normalization beyond ASCII-only validation | + +--- + +## Record types & API + +| Priority | Item | +|----------|------| +| P2 | Additional typed RR builders beyond initial set (section 4 of setup spec) | +| P3 | **Zone diff** helper — compare two `Zone` values for automation | + +--- + +## Infrastructure + +| Priority | Item | +|----------|------| +| P3 | Example CLI or small tool wrapping `godns` (library remains primary deliverable) | diff --git a/docs/changes/00001-setup-project/plan.md b/docs/changes/00001-setup-project/plan.md new file mode 100644 index 0000000..f5ff424 --- /dev/null +++ b/docs/changes/00001-setup-project/plan.md @@ -0,0 +1,210 @@ +# Plan: Initial project setup and DNS operation catalog + +## Summary + +Complete **go-dns** project scaffolding and documentation alignment for change **00001-setup-project**: git repository and feature branch, verify workspace registration and existing specify-phase artifacts (`go.mod`, Cursor rule, project spec, README, backlog, `.gitignore`), and **create** `docs/bind-tsig-configuration.md` per spec section 9. Preserve the full DNS operation catalog in `spec.md` (sections 1–10) without rewriting it. + +This change is **setup + documentation only**. It does **not** implement RFC 2136, AXFR, TSIG signing, typed RR builders, zone data types in Go, unit tests, BIND testcontainer integration tests, CLI, or Docker. + +## Prerequisites + +- Approved `spec.md` in this change folder. +- Workspace root `go.work` already lists `./go-dns` and declares `go 1.26.4` — verify only; no edit expected unless sync fails. +- Specify-phase artifacts already exist — **verify and preserve**; apply **minimal fixes** only when misaligned with `spec.md` (broken links, missing topics, `.env` in `.gitignore`). +- **Git (implement-agent, before other changes):** Per `implement-change` skill and go-storywriter **00001** pattern: + - `git init` in `go-dns/` if `.git` is missing. + - Add remote `origin` → `ssh://git@gitea.neitzel.de:2222/konrad/go-dns.git` if absent. + - Checkout branch `feature/C-00001-setup-project`. + - Baseline commit (`chore: baseline before change C-00001-setup-project`) and `git push -u origin feature/C-00001-setup-project`. + - **Risk:** Gitea repository `konrad/go-dns` may need to be created on the server before push succeeds; if push fails, stop and report — do not proceed until resolved. +- Go toolchain **1.26.4** available locally. +- No third-party Go dependencies for this change (no application `.go` files). + +## Implementation steps + +### Step 0 — Git workspace setup + +Run from `go-dns/` per Prerequisites. Establish the feature branch and baseline commit **before** creating or modifying scaffolding files in steps 1–11. + +### Step 1 — Verify Go module + +1. Confirm `go.mod` exists with: + - `module gitea.neitzel.de/konrad/go-dns` + - `go 1.26.4` +2. No `require` block expected (no dependencies until a future library change). +3. Fix only if module path or Go version diverges from spec. + +### Step 2 — Verify workspace registration + +1. From workspace root: confirm `grep -q go-dns go.work`. +2. Run `go work sync` from workspace root. +3. If sync fails, resolve `go.mod` issues from step 1 and retry. + +### Step 3 — Verify Cursor project rule + +Confirm `.cursor/rules/project-specification.mdc`: + +- Front matter: `alwaysApply: true`, short `description` for go-dns. +- Body: pointer to `docs/project-specification.md` as authoritative spec. +- Brief stack summary: Go library, module path, **`godns`** package at module root, BIND target, TSIG, AXFR-only transfer, typed RR builders (future), BIND testcontainer (future integration tests). +- Note that project `.cursor` overrides workspace defaults when they conflict. + +Follow patterns from `go-ollama/.cursor/rules/project-specification.mdc` and `log-analyzer/.cursor/rules/project-specification.mdc`. **Minimal edits only** if topics are missing or contradict resolved decisions. + +### Step 4 — Verify project specification document + +Confirm `docs/project-specification.md` (English) covers all required topics: + +| Section | Content | +|---------|---------| +| **Overview** | Library for RFC 2136 dynamic updates and **AXFR** zone transfer against BIND; TSIG auth; typed zone data and RR builders (future). | +| **Module metadata** | Path `gitea.neitzel.de/konrad/go-dns`, public package **`godns`**, Go 1.26.4, Gitea remote URL. | +| **Target environment** | BIND 9.x; UPDATE with **automatic TCP fallback**; AXFR over TCP; TSIG required; **ASCII-only** names in v1; **no IXFR** in v1. | +| **Operation surface** | Pointer to change **00001** `spec.md` as authoritative catalog (sections 1–10). | +| **Zone data model** | Logical types `Record`, `RRSet`, `Zone`, `UpdateResult`, `TransferResult`. | +| **BIND configuration** | Pointer to `docs/bind-tsig-configuration.md` (after step 8). | +| **API style** | Typed builders per RR type; no generic rdata string as primary API. | +| **Change workflow** | `docs/changes/` efforts; backlog at `docs/backlog.md`. | +| **Tests (future)** | Unit `go test ./...`; integration `go test -tags=integration ./...` with **BIND testcontainer** — not implemented in this change. | +| **Package layout (planned)** | `package godns` at module root; `internal/` for wire protocol (future). | + +After step 8, ensure the BIND section links to the live `docs/bind-tsig-configuration.md` path (not only “written during implementation”). + +### Step 5 — Preserve backlog + +1. Confirm `docs/backlog.md` exists with a prioritized **IXFR** entry under Zone transfer (P2 per specify phase). +2. **Do not** recreate or substantially rewrite; minimal link fixes only if needed. + +### Step 6 — Verify `.gitignore` + +Confirm exclusions for: + +- Go binaries and test artifacts (`*.test`, `*.out`, project binary names) +- IDE/OS junk (`.idea/`, `.vscode/`, `.DS_Store`) +- Local secrets: `testconfig/local.toml` (already present); add `.env` / `.env.*` if missing per spec requirement 10 + +### Step 7 — Verify project README + +Confirm `README.md` (English): + +- Describes library intent: dynamic updates (RFC 2136) and **AXFR** zone transfer +- States early setup status and links to `docs/changes/00001-setup-project/spec.md` +- Documents module path and **`godns`** import name +- Links to `docs/project-specification.md` and `docs/backlog.md` +- After step 8, add link to `docs/bind-tsig-configuration.md` + +No build/run instructions for a binary (library-only; no `cmd/` in this change). + +### Step 8 — Create BIND TSIG configuration guide + +Create `docs/bind-tsig-configuration.md` (English). This is the **primary new deliverable** of this change. Content MUST satisfy spec section 9: + +| # | Requirement | Guide content | +|---|-------------|---------------| +| 1 | **Define the key** | `key "name" { algorithm ...; secret "..."; };` in `named.conf` or included file. State explicitly that **key generation is out of library scope**; operators use `tsig-keygen`, `rndc tsig-keygen`, or secret management. | +| 2 | **Zone ACLs** | For `zone "example.com" { ... }`: **`allow-update { key "name"; };`** and **`allow-transfer { key "name"; };`** for the same TSIG key. | +| 3 | **Stricter policy** | Mention **`update-policy`** as optional supplement or alternative to broad `allow-update`. | +| 4 | **Reload / validation** | `named-checkconf`, `rndc reload`, verify with `nsupdate -y` and TSIG-signed AXFR (`dig -y` or equivalent). | +| 5 | **Security notes** | Restrict key to needed zones; do not expose AXFR publicly; separate read vs write keys when policy requires (library may use different keys per client). | +| 6 | **Minimal example** | Complete `named.conf` fragment: `key` block + `zone` block with `allow-update` and `allow-transfer` for one key (expand the illustrative fragment in spec section 9). | + +Suggested structure: introduction → key definition → zone configuration → optional `update-policy` → reload and verification → security → full example → related docs (link to `spec.md`, `project-specification.md`). + +### Step 9 — Cross-document alignment pass + +After the BIND guide exists: + +1. Update `docs/project-specification.md` BIND section with a direct link to `bind-tsig-configuration.md`. +2. Update `README.md` documentation links if not done in step 7. +3. Confirm `.cursor/rules/project-specification.mdc` still accurately references the guide path. +4. Re-read resolved decisions in `spec.md` — no contradictions (godns, typed builders deferred, AXFR-only, BIND testcontainer deferred, TCP fallback deferred, ASCII-only deferred, no key generation). + +### Step 10 — Change documentation layout + +1. Confirm `docs/changes/` exists. +2. This folder `docs/changes/00001-setup-project/` contains `spec.md` (specify phase) and `plan.md` (plan phase). +3. `verification.md` and `changes.md` are **out of scope** for implement phase (verify-agent / document-agent). + +### Step 11 — Local verification (implement-agent quick check) + +From workspace root and `go-dns/`: + +```bash +cd /Users/konrad/Projects/go && go work sync +test -f go-dns/docs/bind-tsig-configuration.md +``` + +No `go test` or `go build` — no Go source in this change. + +## Files to create or modify + +| Path | Action | Purpose | +|------|--------|---------| +| `.git/` + branch `feature/C-00001-setup-project` | create (implement-agent, step 0) | Git repo, remote, baseline push | +| `go.mod` | verify/preserve | Module identity and Go version (specify phase) | +| `go.work` (workspace root) | verify only | Already lists `./go-dns` | +| `.cursor/rules/project-specification.mdc` | verify/preserve | Cursor always-on project rule | +| `docs/project-specification.md` | verify/modify | Authoritative project spec; link BIND guide after step 8 | +| `docs/backlog.md` | verify/preserve | IXFR backlog entry | +| `.gitignore` | verify/modify | Go artifacts + secret paths (add `.env` if missing) | +| `README.md` | verify/modify | Project overview; link BIND guide after step 8 | +| `docs/bind-tsig-configuration.md` | **create** | BIND `allow-update` / `allow-transfer` TSIG guide | +| `docs/changes/00001-setup-project/spec.md` | verify/preserve | Full operation catalog — **do not rewrite** | +| `docs/changes/00001-setup-project/plan.md` | create | This plan (plan phase) | + +**Not created in this change:** any `*.go` files, `internal/`, `cmd/`, unit tests, integration tests (`*_integration_test.go`), `docker-compose.yml`, `frontend/`, `verification.md`, `changes.md`. + +## Dependencies and risks + +| Risk | Mitigation | +|------|------------| +| Gitea remote repo does not exist | Create `konrad/go-dns` on Gitea before baseline push; implement-agent stops on push failure | +| `go work sync` fails | Ensure valid `go.mod` before sync | +| Scope creep (RFC 2136, AXFR, TSIG code, testcontainers) | Strictly follow spec out-of-scope; documentation and scaffolding only | +| Rewriting `spec.md` operation catalog | Preserve specify-phase content; verify AC-6 only | +| BIND guide incomplete | Use spec section 9 checklist as implement-agent acceptance gate before handoff | +| Doc drift between README, project-spec, cursor rule | Step 9 alignment pass | + +**External dependencies:** None (no Go packages, no Docker for this change). + +**Integration tests:** Not applicable in this change. Future library changes will use **BIND testcontainer** via testcontainers-go with build tag `integration` and command `go test -tags=integration ./...` (Docker required in CI) per workspace `integration-testing.mdc` and spec resolved decisions. Document in project-spec and cursor rule only — **do not implement** testcontainers or BIND image wiring here. + +**Future changes (out of scope, for traceability):** + +| Topic | When | +|-------|------| +| `package godns` public API and `internal/` wire protocol | After setup verified | +| Typed RR builders (A, AAAA, MX, …) | Dedicated change | +| Zone data types in Go (`Record`, `Zone`, …) | With or after first protocol change | +| RFC 2136 UPDATE + automatic TCP fallback | Library implementation change | +| AXFR + TSIG signing | Library implementation change | +| BIND testcontainer integration tests | After minimal library surface exists | +| IXFR | Backlog P2 — `docs/backlog.md` | + +## Test strategy + +| Criterion | Verification method | +|-----------|----------------------| +| **AC-1** | From workspace root: `grep -q go-dns /Users/konrad/Projects/go/go.work` exits 0; `cd /Users/konrad/Projects/go && go work sync` exits 0 | +| **AC-2** | `grep -E '^module gitea.neitzel.de/konrad/go-dns$' /Users/konrad/Projects/go/go-dns/go.mod` and `grep -E '^go 1.26.4$' /Users/konrad/Projects/go/go-dns/go.mod` | +| **AC-3** | `test -f go-dns/.cursor/rules/project-specification.mdc`; `grep -q 'alwaysApply: true' go-dns/.cursor/rules/project-specification.mdc`; `grep -q 'docs/project-specification.md' go-dns/.cursor/rules/project-specification.mdc` | +| **AC-4** | `test -f go-dns/docs/project-specification.md`; `rg -i 'gitea.neitzel.de/konrad/go-dns|godns|BIND|RFC 2136|AXFR|TSIG|Record|RRSet|Zone|UpdateResult|TransferResult|bind-tsig-configuration' go-dns/docs/project-specification.md` returns matches for module path, BIND target, protocols, TSIG, zone model, and BIND guide reference | +| **AC-5** | `test -f go-dns/README.md`; `rg -i 'update|AXFR|gitea.neitzel.de/konrad/go-dns|godns|00001-setup-project/spec' go-dns/README.md` | +| **AC-6** | `test -f go-dns/docs/changes/00001-setup-project/spec.md`; manual/doc review — sections 1–10 present: session/auth, updates, prerequisites, RR types, update submission, AXFR (no IXFR as v1 feature), zone data model, auxiliary, BIND guide requirements, non-goals. Command aid: `rg -n '^### [0-9]+\.' go-dns/docs/changes/00001-setup-project/spec.md` shows numbered sections 1–10 under “DNS operations” and section 9 “BIND configuration” / section 10 “Explicit non-goals” | +| **AC-7** | **(a)** Spec requirement: `rg -i 'bind-tsig-configuration|allow-update|allow-transfer' go-dns/docs/changes/00001-setup-project/spec.md` matches. **(b)** After implement: `test -f go-dns/docs/bind-tsig-configuration.md`; content checklist — `rg -i 'allow-update|allow-transfer|tsig-keygen|named-checkconf|rndc reload|update-policy|hmac-sha256' go-dns/docs/bind-tsig-configuration.md`; manual review confirms minimal `key` + `zone` `named.conf` example and security notes | +| **AC-8** | `test -f go-dns/docs/backlog.md`; `rg -i 'IXFR' go-dns/docs/backlog.md` | +| **AC-9** | `test -f go-dns/docs/changes/00001-setup-project/spec.md`; `test -f go-dns/docs/changes/00001-setup-project/plan.md`; after verify/document phases: `verification.md` and `changes.md` in same folder | +| **AC-10** | `rg -i 'godns|typed builder|AXFR|BIND testcontainer|TCP fallback|ASCII|git init' go-dns/docs/changes/00001-setup-project/spec.md` matches [Resolved decisions](#resolved-decisions) table | + +**Integration test command:** N/A for this change (BIND testcontainer deferred to a future change). + +**Unit test command:** N/A — no Go source files; `go test ./...` has nothing to run until a later change adds packages. + +**Docker required:** No (for this change). + +## Rollback notes + +- **Code/docs:** Revert commits on `feature/C-00001-setup-project` or remove files created in this change; preserve `docs/changes/` history if useful. +- **Workspace:** `go.work` entry for `./go-dns` can remain (harmless) or be removed manually if fully abandoning the project. +- **Remote:** No destructive git operations; branch can be deleted on Gitea manually if the effort is cancelled after push. +- **Runtime:** No deployed services or containers for this change. diff --git a/docs/changes/00001-setup-project/spec.md b/docs/changes/00001-setup-project/spec.md new file mode 100644 index 0000000..c331679 --- /dev/null +++ b/docs/changes/00001-setup-project/spec.md @@ -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 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. diff --git a/docs/project-specification.md b/docs/project-specification.md new file mode 100644 index 0000000..2cffd1f --- /dev/null +++ b/docs/project-specification.md @@ -0,0 +1,84 @@ +# go-dns — Project Specification + +## Overview + +**go-dns** is a Go library for authoritative DNS operations against **BIND** (and compatible servers): + +- **Dynamic updates** — same semantics as `nsupdate` ([RFC 2136](https://www.rfc-editor.org/rfc/rfc2136)) +- **Zone transfer** — full **AXFR** only ([RFC 5936](https://www.rfc-editor.org/rfc/rfc5936)); IXFR is backlog +- **TSIG authentication** — callers supply existing shared keys; key generation is out of scope + +Callers perform updates and full zone reads in Go with typed **zone data structures** and **typed RR builders** instead of shelling out to `nsupdate` or `dig axfr`. + +## Module metadata + +| Item | Value | +|------|-------| +| Module path | `gitea.neitzel.de/konrad/go-dns` | +| Go version | 1.26.4 (workspace `go.work`) | +| Public package | **`godns`** (`package godns` at module root) | +| Git remote (implementation) | `ssh://git@gitea.neitzel.de:2222/konrad/go-dns.git` | + +## Target environment + +| Item | Value | +|------|-------| +| Nameserver | BIND 9.x (primary reference) | +| Protocols | RFC 2136 UPDATE (UDP + **automatic TCP fallback**); **AXFR over TCP** | +| Authentication | TSIG (RFC 2845 / RFC 8945) — required for documented happy path | +| Domain names | **ASCII only** in v1 (no IDNA) | +| Out of scope (initially) | IXFR, full DNS server, recursive resolver, TSIG key generation, IDNA | + +## Supported operation surface + +The authoritative list lives in change **00001-setup-project** ([spec.md](changes/00001-setup-project/spec.md)). Summary: + +1. **Session & TSIG key** — server, zone, transport, existing key (name, algorithm, secret) +2. **Dynamic update** — add, delete, prerequisites, batch UPDATE; typed RR builders +3. **Zone transfer** — **AXFR** only; parse stream into zone data +4. **Zone data model** — `Record`, `RRSet`, `Zone`, `UpdateResult`, `TransferResult` +5. **BIND guide** — `docs/bind-tsig-configuration.md` (how to set `allow-update` + `allow-transfer` for a key) + +Future work: [backlog.md](backlog.md) (includes **IXFR**). + +## API style + +- **Typed builders** per RR type (e.g. A, AAAA, MX, SRV) — not a generic `(type, rdata string)` API +- Exact method names follow in subsequent changes after the catalog is agreed + +## Zone data model (logical) + +| Type | Purpose | +|------|---------| +| **Record** | Single RR: owner, class, type, TTL, typed rdata | +| **RRSet** | Group of RRs with same owner/class/type | +| **Zone** | Origin + full record set + SOA/serial (complete zone in memory) | +| **UpdateResult** | UPDATE response status | +| **TransferResult** | AXFR result + populated `Zone` + serial metadata | + +## BIND configuration + +Operators configure BIND so a TSIG key may **update** and **transfer** a zone (`allow-update`, `allow-transfer`). Requirements and example are in the change spec; the standalone guide will live at **`docs/bind-tsig-configuration.md`** (written during implementation). + +## Change workflow + +Features are delivered through documented changes under `docs/changes/-/` (spec → plan → implement → verify → changes). The product backlog lives at [backlog.md](backlog.md). + +## Tests + +| Command | Purpose | +|---------|---------| +| `go test ./...` | Unit tests (encoding, parsing, zone model, typed builders) | +| `go test -tags=integration ./...` | **BIND testcontainer** (Docker required) | + +Integration tests use testcontainers-go with a BIND image per workspace `integration-testing.mdc`. + +## Package layout (planned) + +``` +package godns # public API at module root +internal/ # RFC 2136, AXFR, TSIG, wire format +docs/ # Project docs incl. bind-tsig-configuration.md, backlog.md +``` + +Layout details are finalized in the setup implementation plan. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7639ce8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.neitzel.de/konrad/go-dns + +go 1.26.4