coredns-rfc2136/setup.go
Ryan Malloy e9d37f483c Initial commit: plugin skeleton, compiles against CoreDNS 1.14.3
Sets up the package layout for a CoreDNS plugin that will accept RFC 2136
dynamic updates with TSIG authentication, primarily targeting self-hosted
ACME DNS-01 cert automation.

What this commit gives us:
- go.mod against coredns/caddy v1.1.4, coredns/coredns v1.14.3, miekg/dns v1.1.72
- plugin.go: RFC2136 struct + Handler interface (ServeDNS is pass-through)
- setup.go: init() registration + Corefile parser (skeleton — recognizes
  tsig-key, ttl, persist directives but doesn't yet wire them)
- README.md, .gitignore

go build ./... clean. No tests yet — those come with Phase 1.2 alongside
the actual UPDATE handler and in-memory store.

Plan: ~/.claude/plans/dood-does-coredns-offer-enumerated-piglet.md
2026-05-20 18:25:36 -06:00

93 lines
2.4 KiB
Go

package rfc2136
import (
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
clog "github.com/coredns/coredns/plugin/pkg/log"
)
// log is the package logger, scoped so messages are prefixed `[rfc2136]`.
var log = clog.NewWithPlugin("rfc2136")
func init() {
plugin.Register("rfc2136", setup)
}
// setup is invoked by the CoreDNS plugin registry once per Corefile
// `rfc2136` directive. It parses the directive's arguments and block,
// constructs an RFC2136 handler, and links it into the plugin chain.
func setup(c *caddy.Controller) error {
p, err := parse(c)
if err != nil {
return plugin.Error("rfc2136", err)
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
p.Next = next
return p
})
log.Infof("registered for zones: %v", p.Zones)
return nil
}
// parse reads a single `rfc2136 <zone> { ... }` block from the Corefile.
//
// Phase 1 grammar (only the surface is parsed; sub-directives are
// accepted but ignored — Phase 2 wires them):
//
// rfc2136 <zone> {
// tsig-key <name> <algorithm> <secret>
// ttl <seconds>
// persist <path>
// }
func parse(c *caddy.Controller) (*RFC2136, error) {
p := &RFC2136{}
for c.Next() {
args := c.RemainingArgs()
if len(args) < 1 {
return nil, c.ArgErr()
}
// Normalize each declared zone to lowercase + trailing dot
// (CoreDNS canonical form). This makes later zone-membership
// checks an exact match against r.Question[0].Name.
for _, z := range args {
p.Zones = append(p.Zones, plugin.Host(z).NormalizeExact()...)
}
for c.NextBlock() {
switch c.Val() {
case "tsig-key":
kArgs := c.RemainingArgs()
if len(kArgs) != 3 {
return nil, c.Errf("tsig-key requires 3 args (name algorithm secret), got %d", len(kArgs))
}
// Phase 2: store in p.tsigKeys[name] = tsigKey{algo, secret}
log.Debugf("tsig-key parsed (storage NYI): name=%s alg=%s", kArgs[0], kArgs[1])
case "ttl":
tArgs := c.RemainingArgs()
if len(tArgs) != 1 {
return nil, c.ArgErr()
}
// Phase 2: parse uint32, validate range, store in p.ttl
log.Debugf("ttl parsed (storage NYI): %s", tArgs[0])
case "persist":
pArgs := c.RemainingArgs()
if len(pArgs) != 1 {
return nil, c.ArgErr()
}
log.Debugf("persist parsed (storage NYI): %s", pArgs[0])
default:
return nil, c.Errf("unknown directive: %s", c.Val())
}
}
}
return p, nil
}