package rfc2136 import ( "strconv" "github.com/coredns/caddy" "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/plugin" clog "github.com/coredns/coredns/plugin/pkg/log" "github.com/miekg/dns" ) // 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 keys=%d ttl=%d persist=%q", p.Zones, len(p.TSIGKeys), p.TTL, p.PersistPath) return nil } // parse reads a single `rfc2136 [...] { ... }` block from // the Corefile and returns a fully-populated RFC2136 handler with all // values validated at parse time (so configuration errors fail fast at // CoreDNS startup, not later mid-request). // // Grammar: // // rfc2136 [...] { // tsig-key ; may repeat // ttl ; default 60 // persist ; default off (in-memory only) // } func parse(c *caddy.Controller) (*RFC2136, error) { p := &RFC2136{ TSIGKeys: make(map[string]tsigKey), TTL: DefaultTTL, store: newStore(), } 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 "nameserver": nArgs := c.RemainingArgs() if len(nArgs) != 1 { return nil, c.ArgErr() } p.Nameserver = dns.Fqdn(nArgs[0]) case "tsig-key": // 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)) } keyName := canonicalKeyName(kArgs[0]) algo, err := parseTSIGAlgorithm(kArgs[1]) if err != nil { return nil, c.Err(err.Error()) } secret, err := decodeTSIGSecret(kArgs[2]) if err != nil { return nil, c.Errf("tsig-key %q: %v", keyName, err) } if _, exists := p.TSIGKeys[keyName]; exists { return nil, c.Errf("duplicate tsig-key %q", keyName) } p.TSIGKeys[keyName] = tsigKey{Algorithm: algo, Secret: secret} case "ttl": tArgs := c.RemainingArgs() if len(tArgs) != 1 { return nil, c.ArgErr() } ttl, err := strconv.ParseUint(tArgs[0], 10, 32) if err != nil { return nil, c.Errf("ttl must be a non-negative integer: %v", err) } // Anything over a week is almost certainly a mistake // for ACME challenge records, but allow up to the // uint32 max so we don't ship an arbitrary cap. p.TTL = uint32(ttl) case "persist": pArgs := c.RemainingArgs() if len(pArgs) != 1 { return nil, c.ArgErr() } p.PersistPath = pArgs[0] default: return nil, c.Errf("unknown directive: %s", c.Val()) } } } if len(p.Zones) == 0 { return nil, c.Err("at least one zone must be specified") } // Default nameserver to the first zone apex. The user can override // via the `nameserver` directive — e.g. when the delegating parent // zone publishes `auth NS dns.supported.systems`, this should be // set to `dns.supported.systems.` to match. if p.Nameserver == "" { p.Nameserver = p.Zones[0] } return p, nil }