coredns-rfc2136/update_test.go

283 lines
8.3 KiB
Go
Raw Normal View History

Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
package rfc2136
import (
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
"net"
"os"
"path/filepath"
"strings"
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
"testing"
"github.com/miekg/dns"
)
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// captureWriter implements dns.ResponseWriter and stashes the message
// passed to WriteMsg so tests can inspect it after handleUpdate returns.
type captureWriter struct {
msg *dns.Msg
}
func (cw *captureWriter) WriteMsg(m *dns.Msg) error { cw.msg = m; return nil }
func (cw *captureWriter) Write([]byte) (int, error) { return 0, nil }
func (cw *captureWriter) Close() error { return nil }
func (cw *captureWriter) TsigStatus() error { return nil }
func (cw *captureWriter) TsigTimersOnly(bool) {}
func (cw *captureWriter) Hijack() {}
func (cw *captureWriter) LocalAddr() net.Addr { return &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} }
func (cw *captureWriter) RemoteAddr() net.Addr { return &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} }
func (cw *captureWriter) Network() string { return "udp" }
// newTestPluginWithZone builds an RFC2136 backed by a temp zones-dir.
// AutoCommit is disabled by default (tests don't need git side-effects).
func newTestPluginWithZone(t *testing.T, zone string) *RFC2136 {
t.Helper()
dir := withTempZonesDir(t, zone)
zone = dns.Fqdn(zone)
p := &RFC2136{
Zones: []string{zone},
TTL: 60,
ZonesDir: dir,
zones: map[string]*zoneFile{
zone: openZoneFile(filepath.Join(dir, strings.TrimSuffix(zone, ".")+".zone"), zone),
},
}
p.zones[zone].AutoCommit = false
return p
}
// newUpdate builds an UPDATE message for `zone` with zero prereqs and
// the given update RRs. Matches what caddy-dns/rfc2136 sends.
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
func newUpdate(zone string, updates ...dns.RR) *dns.Msg {
m := new(dns.Msg)
m.SetUpdate(dns.Fqdn(zone))
m.Ns = append(m.Ns, updates...)
return m
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// runUpdate sends msg through the handler with TSIG auth bypassed
// (calling handleUpdate directly instead of ServeDNS).
func runUpdate(t *testing.T, p *RFC2136, msg *dns.Msg) (rcode int) {
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
t.Helper()
w := &captureWriter{}
rcode, _ = p.handleUpdate(w, msg)
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
return rcode
}
// readZoneRecords loads the zone file and returns the RRs for inspection.
func readZoneRecords(t *testing.T, p *RFC2136, zone string) []dns.RR {
t.Helper()
zf := p.zones[dns.Fqdn(zone)]
rrs, err := zf.loadRRs()
if err != nil {
t.Fatalf("loadRRs: %v", err)
}
return rrs
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_AddSingleTXT_PersistsToFile(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
upd := newUpdate("auth.example.com",
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
mustRR(t, `token-1.auth.example.com. 60 IN TXT "validation-1"`),
)
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, upd); rcode != dns.RcodeSuccess {
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
t.Fatalf("rcode = %d, want NOERROR", rcode)
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// Verify by re-reading the file.
rrs := readZoneRecords(t, p, "auth.example.com")
for _, rr := range rrs {
if txt, ok := rr.(*dns.TXT); ok && txt.Hdr.Name == "token-1.auth.example.com." {
if txt.Txt[0] == "validation-1" {
return // success
}
}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
t.Errorf("added TXT not found in re-read zone file")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_AddBumpsSerial(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
before := readZoneRecords(t, p, "auth.example.com")
var beforeSerial uint32
for _, rr := range before {
if soa, ok := rr.(*dns.SOA); ok {
beforeSerial = soa.Serial
break
}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
upd := newUpdate("auth.example.com",
mustRR(t, `foo.auth.example.com. 60 IN TXT "x"`),
)
runUpdate(t, p, upd)
after := readZoneRecords(t, p, "auth.example.com")
var afterSerial uint32
for _, rr := range after {
if soa, ok := rr.(*dns.SOA); ok {
afterSerial = soa.Serial
break
}
}
if afterSerial <= beforeSerial {
t.Errorf("Serial did not advance: before=%d after=%d", beforeSerial, afterSerial)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_DeleteRRset_RemovesAllOfType(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
// First add two TXTs at the same name.
runUpdate(t, p, newUpdate("auth.example.com",
mustRR(t, `foo.auth.example.com. 60 IN TXT "a"`),
mustRR(t, `foo.auth.example.com. 60 IN TXT "b"`),
))
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// Now delete the whole TXT RRset.
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
del := &dns.ANY{Hdr: dns.RR_Header{
Name: "foo.auth.example.com.",
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
Rrtype: dns.TypeTXT,
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Class: dns.ClassANY,
}}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, newUpdate("auth.example.com", del)); rcode != dns.RcodeSuccess {
t.Fatalf("delete rcode = %d", rcode)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
for _, rr := range readZoneRecords(t, p, "auth.example.com") {
if rr.Header().Rrtype == dns.TypeTXT && rr.Header().Name == "foo.auth.example.com." {
t.Errorf("TXT %s should be gone, still present: %s", rr.Header().Name, rr.String())
}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_OutOfZone_Refused(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
upd := newUpdate("auth.example.com",
mustRR(t, `evil.other.tld. 60 IN TXT "nope"`),
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
)
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, upd); rcode != dns.RcodeNotZone {
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
t.Errorf("rcode = %d, want NOTZONE (%d)", rcode, dns.RcodeNotZone)
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_UnknownZone_NOTAUTH(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// Build UPDATE for a zone we don't manage.
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
m := new(dns.Msg)
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
m.SetUpdate("other.zone.")
m.Ns = []dns.RR{mustRR(t, `x.other.zone. 60 IN TXT "y"`)}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, m); rcode != dns.RcodeNotAuth {
t.Errorf("rcode = %d, want NOTAUTH (%d)", rcode, dns.RcodeNotAuth)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_ApexSOADeletion_Refused(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// CLASS=ANY type=SOA at apex → would wipe the SOA.
del := &dns.ANY{Hdr: dns.RR_Header{
Name: "auth.example.com.",
Rrtype: dns.TypeSOA,
Class: dns.ClassANY,
}}
if rcode := runUpdate(t, p, newUpdate("auth.example.com", del)); rcode != dns.RcodeRefused {
t.Errorf("rcode = %d, want REFUSED", rcode)
}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// SOA must still be in the file.
hasSOA := false
for _, rr := range readZoneRecords(t, p, "auth.example.com") {
if _, ok := rr.(*dns.SOA); ok {
hasSOA = true
break
}
}
if !hasSOA {
t.Errorf("SOA was deleted despite REFUSED rcode")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_ApexWipe_Refused(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
del := &dns.ANY{Hdr: dns.RR_Header{
Name: "auth.example.com.",
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Rrtype: dns.TypeANY,
Class: dns.ClassANY,
}}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, newUpdate("auth.example.com", del)); rcode != dns.RcodeRefused {
t.Errorf("rcode = %d, want REFUSED", rcode)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
func TestUpdate_PrereqRRsetMustNotExist_YXRRSET(t *testing.T) {
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
p := newTestPluginWithZone(t, "auth.example.com")
// Seed a TXT then send an UPDATE whose prereq says "no TXT here".
runUpdate(t, p, newUpdate("auth.example.com",
mustRR(t, `foo.auth.example.com. 60 IN TXT "present"`),
))
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
prereq := &dns.ANY{Hdr: dns.RR_Header{
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
Name: "foo.auth.example.com.",
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Rrtype: dns.TypeTXT,
Class: dns.ClassNONE,
}}
m := new(dns.Msg)
m.SetUpdate("auth.example.com.")
m.Answer = []dns.RR{prereq}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
m.Ns = []dns.RR{mustRR(t, `foo.auth.example.com. 60 IN TXT "should-not-be-added"`)}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
if rcode := runUpdate(t, p, m); rcode != dns.RcodeYXRrset {
t.Errorf("rcode = %d, want YXRRSET", rcode)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestUpdate_NoOpAdd_DoesntRewriteFile(t *testing.T) {
p := newTestPluginWithZone(t, "auth.example.com")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// Add a record once.
runUpdate(t, p, newUpdate("auth.example.com",
mustRR(t, `foo.auth.example.com. 60 IN TXT "x"`),
))
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
path := p.zones["auth.example.com."].Path
beforeStat, _ := os.Stat(path)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
// Same UPDATE again — should be a no-op (dedupe).
runUpdate(t, p, newUpdate("auth.example.com",
mustRR(t, `foo.auth.example.com. 60 IN TXT "x"`),
))
afterStat, _ := os.Stat(path)
if afterStat.ModTime().After(beforeStat.ModTime()) {
t.Errorf("file was rewritten despite no-op update (mtime advanced)")
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestFindZone_LongestSuffixWins(t *testing.T) {
p := &RFC2136{Zones: []string{"example.com.", "auth.example.com."}}
if got := p.findZone("foo.auth.example.com."); got != "auth.example.com." {
t.Errorf("findZone returned %q, expected longest match", got)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
}
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestFindZone_OutsideAllZones(t *testing.T) {
p := &RFC2136{Zones: []string{"auth.example.com."}}
if got := p.findZone("other.tld."); got != "" {
t.Errorf("findZone returned %q, want empty", got)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}
Phase 2b: refactor to file-backed storage; UPDATE writes zones/*.zone Major architectural pivot per the user's "RFC 2136 mechanism for the existing zonefiles, not a new in-memory thing" framing. The plugin no longer maintains its own in-memory state OR serves any queries -- both of those are now the auto plugin's job, reading the same zone files. The plugin's sole responsibility is now: receive TSIG-authed UPDATE messages, edit the matching zones/<zone>.zone file, bump the SOA serial in CalVer (YYYYMMDDNN) form, and optionally auto-commit to git. What changed: - DELETED: store.go (in-memory recordStore), store_test.go (12 tests), plugin_test.go (10 ServeDNS query tests), old update_test.go. - NEW: zonefile.go -- file-backed authority for one zone. loadRRs via miekg/dns zone parser; mutation helpers (lookupIn/nameExistsIn/ removeRRsetFrom/removeRRFrom/removeNameFrom/addRRTo) on []dns.RR slices; bumpSerial with CalVer semantics + NN exhaustion handling; writeAtomic via temp-file rename; commit shells to `git add && git commit` with configurable author. - NEW: zonefile_test.go -- 17 tests covering load/lookup/mutate/bump/ write paths. - REWRITTEN: plugin.go -- ServeDNS is now thin: UPDATE → TSIG → handler; everything else → Next. No synthetic SOA/NS, no query serving. - REWRITTEN: update.go -- handleUpdate now opens the zoneFile, loads, applies (with prereq checks against the loaded RRs), bumps serial, writes, commits. Detects no-op updates to avoid spurious file writes. - REWRITTEN: setup.go -- new directives: `zones-dir` (required), `auto-commit` (default true), `git-author <name> <email>`. Dropped `nameserver` and `persist`. Validates each declared zone has a file on disk via os.Stat before CoreDNS finishes starting. - REWRITTEN: setup_test.go -- 17 cases for the new grammar. - REWRITTEN: update_test.go -- 11 cases using real temp zone files via t.TempDir(). Total: 30 tests passing, 0 failures. Next: Phase 2c (custom CoreDNS image, deploy, smoke test with nsupdate).
2026-05-21 11:26:50 -06:00
func TestFindZone_CaseInsensitive(t *testing.T) {
p := &RFC2136{Zones: []string{"auth.example.com."}}
if got := p.findZone("Foo.AUTH.example.COM."); got != "auth.example.com." {
t.Errorf("case-insensitive findZone returned %q", got)
Phase 1.4: UPDATE opcode handler + TSIG verification Replaces the Phase-1.3 refuseUpdate() stub with a real RFC 2136 handler. Caddy via caddy-dns/rfc2136 can now inject and remove records. UPDATE message handling (update.go): - Zone section validation: must be exactly one SOA-typed record naming a zone we're authoritative for. Returns FORMERR/NOTAUTH otherwise. - Prerequisites (§3.2): name-exists, RRset-exists, name-NOT-exists, RRset-NOT-exists semantics implemented. First failure short-circuits with the spec's rcode (NXDOMAIN/NXRRSET/YXDOMAIN/YXRRSET). - Updates (§3.4.2): add RR, delete RRset (CLASS=ANY+RDLEN=0), delete all RRsets at name (CLASS=ANY+TYPE=ANY), delete specific RR (CLASS= NONE). - Apex SOA/NS protected: synthetic and cannot be added or removed via UPDATE. Apex wipe (TYPE=ANY at apex) also refused. - Default TTL applied to incoming records with TTL=0. TSIG (tsig.go + setup.go): - setup() now populates dnsserver.Config.TsigSecret so the underlying dns.Server auto-verifies signatures via miekg/dns. - checkTSIG() in ServeDNS gates UPDATEs: rejects if no TSIG, unknown key name, algorithm-downgrade attempt, or w.TsigStatus() != nil. - No TSIG keys configured → all UPDATEs refused (safety default). - Algorithm pinning prevents downgrade attacks (e.g. forced HMAC-MD5). Tests (update_test.go): 11 new cases covering happy paths and every error rcode. Total: 35 top-level test passes, 0 failures. ServeDNS dispatch now calls handleUpdate after auth gate. The refuseUpdate() stub is gone. UPDATE end-to-end via nsupdate requires the custom CoreDNS image (Phase 2) to verify TSIG plumbing on the dns.Server side.
2026-05-21 10:51:18 -06:00
}
}