The Corefile parser now fully populates typed fields on RFC2136 instead of just recognising directives. Validation happens at parse-time so configuration errors fail loud at CoreDNS startup rather than silent at request time. Added: - config.go: tsigKey type, TSIG algorithm allowlist (rejects HMAC-MD5 deliberately), base64 secret decoder with 8-byte minimum length check, canonical-key-name normalisation (lowercase + trailing dot). - plugin.go: RFC2136 struct now carries TSIGKeys map, TTL uint32, PersistPath string. DefaultTTL=60. - setup.go: parse() validates and stores tsig-key/ttl/persist directives. Duplicate key names rejected. Multiple TSIG keys allowed (for rotation). At-least-one-zone is enforced. - setup_test.go: 13 table-driven cases (5 happy + 8 error paths) using caddy.NewTestController. All pass. ServeDNS still passes through — UPDATE handling lands in Phase 1.4. Module path: git.supported.systems/rsp2k/coredns-rfc2136
79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
package rfc2136
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// tsigKey is a single pre-shared TSIG credential. The plugin holds one
|
|
// per declared `tsig-key` directive, keyed in RFC2136.TSIGKeys by the
|
|
// canonical (lowercased, trailing-dot) key name.
|
|
//
|
|
// Phase 1.2 stores the algorithm and decoded secret. Phase 1.4 will
|
|
// use them for actual signature verification via dns.TsigSecret +
|
|
// dns.Msg.IsTsig() / dns.TsigVerify().
|
|
type tsigKey struct {
|
|
// Algorithm is the canonical miekg/dns algorithm identifier, e.g.
|
|
// dns.HmacSHA256 ("hmac-sha256."). Stored in dns-library form so
|
|
// it's directly usable when wiring TSIG verification later.
|
|
Algorithm string
|
|
|
|
// Secret is the base64-decoded raw bytes of the shared secret.
|
|
// Decoding at parse time means a malformed secret fails Corefile
|
|
// load (loud), not mid-query (silent).
|
|
Secret []byte
|
|
}
|
|
|
|
// supportedTSIGAlgorithms maps user-facing algorithm names (as written
|
|
// in Corefile) to the canonical names recognised by miekg/dns. Only
|
|
// SHA-family entries are included — HMAC-MD5 is deprecated and not
|
|
// supported here on principle.
|
|
var supportedTSIGAlgorithms = map[string]string{
|
|
"hmac-sha1": dns.HmacSHA1,
|
|
"hmac-sha224": dns.HmacSHA224,
|
|
"hmac-sha256": dns.HmacSHA256,
|
|
"hmac-sha384": dns.HmacSHA384,
|
|
"hmac-sha512": dns.HmacSHA512,
|
|
}
|
|
|
|
// parseTSIGAlgorithm validates and canonicalises the algorithm name
|
|
// from a `tsig-key` directive. Returns the miekg/dns canonical form
|
|
// (which has a trailing dot per DNS-name convention).
|
|
func parseTSIGAlgorithm(s string) (string, error) {
|
|
canon, ok := supportedTSIGAlgorithms[strings.ToLower(s)]
|
|
if !ok {
|
|
return "", fmt.Errorf("unsupported TSIG algorithm %q (supported: hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384, hmac-sha512)", s)
|
|
}
|
|
return canon, nil
|
|
}
|
|
|
|
// decodeTSIGSecret validates the base64-encoded TSIG secret from the
|
|
// Corefile and returns the raw bytes. Rejects empty secrets and any
|
|
// secret shorter than 8 bytes (well under the HMAC algorithm's block
|
|
// size for any supported algorithm — anything that short is almost
|
|
// certainly a typo, not deliberate).
|
|
func decodeTSIGSecret(s string) ([]byte, error) {
|
|
raw, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid base64 in TSIG secret: %w", err)
|
|
}
|
|
if len(raw) < 8 {
|
|
return nil, fmt.Errorf("TSIG secret too short (%d bytes after base64-decode; need >= 8)", len(raw))
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// canonicalKeyName normalises a TSIG key name to lowercase + trailing
|
|
// dot. miekg/dns expects the dot-terminated FQDN form internally, so
|
|
// we coerce here once at parse-time rather than every lookup.
|
|
func canonicalKeyName(name string) string {
|
|
name = strings.ToLower(name)
|
|
if !strings.HasSuffix(name, ".") {
|
|
name += "."
|
|
}
|
|
return name
|
|
}
|