H6/H7/M3/M4/M7: hardening + behavior documentation
H6 — TSIG replay-window test. New TestCheckTSIG_BadStatus_Refused verifies that when miekg/dns reports a TSIG verification failure via ResponseWriter.TsigStatus (the channel for fudge-window violations, bad MACs, expired timestamps), our plugin refuses. The fudge tolerance itself is miekg/dns's default (300s); documented in tsig.go so operators know the dependency. H7 — No-op UPDATE policy: documented explicitly in update.go. We do NOT bump the SOA on a no-op (deduped) UPDATE — forcing downstream secondaries to AXFR identical content wastes bandwidth and contradicts RFC 2136's intent. Callers wanting to force a serial bump can send a throwaway add+delete pair (touch-UPDATE pattern). M3 — Delete-by-exact-match ignores TTL and class per RFC 2136 §2.5.4. The previous rr.String() comparison included TTL, so an UPDATE with CLASS=NONE TTL=0 (the protocol-required encoding for a delete) failed to match stored RRs at CLASS=IN with non-zero TTL. Now we normalize both sides (TTL=0, class=IN) before invoking dns.IsDuplicate. M4 — validateZoneFiles now actually parses each zone at startup (loadRRs invocation). Previously it only stat()'d the file; corrupt zone content sailed through startup and produced SERVFAIL on the first UPDATE with no startup-time signal. Combined with H3+H4's invariant checks, this turns silent zone corruption into immediate startup failure. M7 — Commit-message sanitization. RR names are attacker-controlled (TSIG only authenticates the sender; the payload is hostile by default). Control characters in commit messages could inject newlines into git log or ANSI sequences into downstream log renderers. New sanitizeForCommitMessage escapes \n, \r, \t, and other C0 controls. New tests: - TestCheckTSIG_BadStatus_Refused (H6) - TestUpdate_DeleteRR_IgnoresTTL (M3) - TestSanitizeForCommitMessage (M7)
This commit is contained in:
parent
d9dad01798
commit
6ab2b6af6d
5 changed files with 185 additions and 15 deletions
31
zonefile.go
31
zonefile.go
|
|
@ -251,13 +251,19 @@ func removeNameFrom(rrs []dns.RR, name string) []dns.RR {
|
|||
}
|
||||
|
||||
// removeRRFrom returns rrs minus the single RR matching the given one
|
||||
// by owner + type + rdata. String() comparison covers rdata exactness.
|
||||
// by owner + type + rdata.
|
||||
//
|
||||
// Hamilton M3: per RFC 2136 §2.5.4, a delete-by-exact-match UPDATE
|
||||
// carries CLASS=NONE and TTL=0 as protocol flags, not as match
|
||||
// criteria. The target must match a stored RR by owner+type+rdata
|
||||
// alone. We normalize both sides to the same class + TTL before
|
||||
// invoking dns.IsDuplicate so the comparison is correct.
|
||||
func removeRRFrom(rrs []dns.RR, target dns.RR) []dns.RR {
|
||||
targetStr := target.String()
|
||||
targetN := normalizeForCompare(target)
|
||||
out := rrs[:0:0]
|
||||
matched := false
|
||||
for _, rr := range rrs {
|
||||
if !matched && rr.String() == targetStr {
|
||||
if !matched && dns.IsDuplicate(normalizeForCompare(rr), targetN) {
|
||||
matched = true
|
||||
continue
|
||||
}
|
||||
|
|
@ -267,17 +273,30 @@ func removeRRFrom(rrs []dns.RR, target dns.RR) []dns.RR {
|
|||
}
|
||||
|
||||
// addRRTo appends rr to rrs unless an identical RR already exists
|
||||
// (de-dupe semantics per RFC 2136 §3.4.2.2).
|
||||
// (de-dupe semantics per RFC 2136 §3.4.2.2). Same normalization as
|
||||
// removeRRFrom — dedupe is TTL- and class-insensitive in the comparison
|
||||
// (though the stored RR retains its original TTL/class).
|
||||
func addRRTo(rrs []dns.RR, rr dns.RR) []dns.RR {
|
||||
target := rr.String()
|
||||
rrN := normalizeForCompare(rr)
|
||||
for _, existing := range rrs {
|
||||
if existing.String() == target {
|
||||
if dns.IsDuplicate(normalizeForCompare(existing), rrN) {
|
||||
return rrs
|
||||
}
|
||||
}
|
||||
return append(rrs, rr)
|
||||
}
|
||||
|
||||
// normalizeForCompare returns a copy of rr with TTL=0 and class=IN so
|
||||
// dns.IsDuplicate can be used to compare by (owner, type, rdata) alone.
|
||||
// Required by RFC 2136 §2.5.4's "TTL and CLASS are flags, not match
|
||||
// criteria" semantics.
|
||||
func normalizeForCompare(rr dns.RR) dns.RR {
|
||||
n := dns.Copy(rr)
|
||||
n.Header().Ttl = 0
|
||||
n.Header().Class = dns.ClassINET
|
||||
return n
|
||||
}
|
||||
|
||||
// serialCounterMul is the multiplier between the date prefix and the
|
||||
// counter in our SOA-serial encoding. The format is YYMMDD*10000 + NNNN,
|
||||
// giving 10000 bumps/day (NNNN ∈ [0001, 9999]). The 2-digit year keeps
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue