From 4f1a35dedb03bced758945b914023d41044b1d58 Mon Sep 17 00:00:00 2001 From: Konrad Neitzel Date: Sat, 5 Sep 2026 14:56:08 +0200 Subject: [PATCH] Add a CAA typed record builder for RFC 8659. CheckDNS needs this so domain files with CAA records can be reconciled instead of failing the zone check. Co-authored-by: Cursor --- README.md | 2 +- record.go | 10 ++++++++++ record_test.go | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4de8776..c8c2e20 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ go get gitea.neitzel.de/konrad/go-dns - **AXFR** — full zone read over TCP - **RFC 2136 UPDATE** — typed add/delete changes (UDP with TCP fallback) - **TSIG** — caller-supplied key name, algorithm, and base64 secret -- **Typed RR builders** — A, AAAA, MX, TXT, CNAME, NS, SRV, PTR (ASCII names) +- **Typed RR builders** — A, AAAA, MX, TXT, CNAME, NS, SRV, PTR, CAA (ASCII names) - **Zone / RRSet / Record** — in-memory zone model from AXFR **Excluded** diff --git a/record.go b/record.go index c8cae68..e09bf77 100644 --- a/record.go +++ b/record.go @@ -124,6 +124,16 @@ func PTR(owner string, ttl uint32, target string) (Record, error) { return fromRR(&dns.PTR{Hdr: rrHeader(owner, dns.TypePTR, ttl), Ptr: name}) } +// CAA builds a certification-authority authorization record (RFC 8659). +func CAA(owner string, ttl uint32, flag uint8, tag, value string) (Record, error) { + return fromRR(&dns.CAA{ + Hdr: rrHeader(owner, dns.TypeCAA, ttl), + Flag: flag, + Tag: tag, + Value: value, + }) +} + // rrHeader builds an INET header. Owner ASCII is checked in fromRR. func rrHeader(owner string, typ uint16, ttl uint32) dns.RR_Header { name, err := fqdnASCII(owner) diff --git a/record_test.go b/record_test.go index 3d1d4ab..11152f5 100644 --- a/record_test.go +++ b/record_test.go @@ -137,6 +137,21 @@ func TestPTRBuilder(t *testing.T) { } } +func TestCAABuilder(t *testing.T) { + t.Parallel() + + rec, err := godns.CAA("example.test", 3600, 0, "issue", "letsencrypt.org") + if err != nil { + t.Fatal(err) + } + if rec.Type != "CAA" { + t.Fatalf("type: %q", rec.Type) + } + if rec.RData != `0 issue "letsencrypt.org"` { + t.Fatalf("rdata: %q", rec.RData) + } +} + func TestBuilderRejectsNonASCIIOwner(t *testing.T) { t.Parallel()