coredns-rfc2136/plugin.go

73 lines
2.8 KiB
Go
Raw Normal View History

// Package rfc2136 implements a CoreDNS plugin that accepts dynamic DNS
// updates per RFC 2136 (UPDATE opcode), authenticated via TSIG. The
// primary use case is self-hosted ACME DNS-01 cert automation: an ACME
// client (e.g. Caddy via caddy-dns/rfc2136) injects _acme-challenge TXT
// records into a delegated sub-zone that this plugin serves.
//
// Scope:
// - Handles UPDATE messages (OPCODE=5) for configured zones.
// - Verifies TSIG signatures (HMAC-SHA family; algorithm-pluggable).
// - Stores records in memory; optional periodic snapshot to disk.
// - Serves queries (SOA, NS, A, AAAA, TXT) for the configured zone
// from the in-memory store plus a synthetic SOA/NS apex.
//
// Non-goals:
// - General-purpose authoritative DNS (use `auto`/`file` for that).
// - DNSSEC signing (add later via the `dnssec` plugin in front).
//
// Phase 1.2 status: parser fully wires Corefile into typed config.
// ServeDNS still passes through to the next plugin — UPDATE handling
// and zone-serving land in Phase 1.3/1.4. See plan at
//
// ~/.claude/plans/dood-does-coredns-offer-enumerated-piglet.md
package rfc2136
import (
"context"
"github.com/coredns/coredns/plugin"
"github.com/miekg/dns"
)
// DefaultTTL is the TTL applied to dynamically-added records when the
// Corefile doesn't specify one. 60s matches the short-lived nature of
// ACME challenge TXT records and keeps stale answers from lingering in
// resolver caches.
const DefaultTTL uint32 = 60
// RFC2136 is the plugin handler. One instance per Corefile server block.
type RFC2136 struct {
// Next is the downstream plugin in the chain.
Next plugin.Handler
// Zones is the set of canonical (dot-terminated, lowercase) zone
// names this instance is authoritative for. Queries outside these
// zones pass through to Next.
Zones []string
// TSIGKeys is keyed by canonical key name (lowercased, trailing
// dot). Empty means TSIG is disabled — UPDATEs without TSIG would
// be rejected unconditionally in Phase 1.4.
TSIGKeys map[string]tsigKey
// TTL is applied to dynamically-injected records that don't carry
// an explicit TTL in the UPDATE message.
TTL uint32
// PersistPath, when non-empty, names a file the plugin writes a
// JSON snapshot of its in-memory store to on a periodic schedule.
// Empty means in-memory only (acceptable for ACME challenges,
// which are seconds-to-minutes lived and re-issued on restart).
PersistPath string
}
// Name implements plugin.Handler.
func (p *RFC2136) Name() string { return "rfc2136" }
// ServeDNS implements plugin.Handler. Phase 1.x is a pass-through so
// the plugin can register, parse config, and live in the chain without
// changing behavior. Phase 1.3 wires UPDATE handling + query serving.
func (p *RFC2136) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
}