feat: add NS TTL consistency and NS-target CNAME checks
Observe the NS RRset TTL from each parent server (ParentView.NSTTL) and whether each NS target name is a CNAME alias (ChildNSView.CNAMETarget). Two new rules judge the collected facts: - delegation_ns_ttl_inconsistent: warns when parent servers disagree on the NS TTL, which indicates zone-data inconsistency between primaries. - delegation_ns_is_cname: flags NS targets that are CNAME aliases as critical, per RFC 2181 §10.3 which forbids aliased NS names.
This commit is contained in:
parent
a16e01e1d4
commit
70c548284e
4 changed files with 125 additions and 6 deletions
|
|
@ -108,15 +108,16 @@ func resolveZoneNSAddrs(ctx context.Context, zone string) ([]string, error) {
|
|||
|
||||
// queryDelegation expects a referral response (no RD) and pulls NS + glue
|
||||
// from every section so misconfigured parents (NS in Answer) still parse.
|
||||
func queryDelegation(ctx context.Context, parentServer, fqdn string) (ns []string, glue map[string][]string, msg *dns.Msg, err error) {
|
||||
// nsTTL is the TTL of the first matching NS record (all RRset members share it).
|
||||
func queryDelegation(ctx context.Context, parentServer, fqdn string) (ns []string, glue map[string][]string, nsTTL uint32, nsTTLKnown bool, err error) {
|
||||
q := dns.Question{Name: dns.Fqdn(fqdn), Qtype: dns.TypeNS, Qclass: dns.ClassINET}
|
||||
|
||||
msg, err = dnsExchange(ctx, "", parentServer, q, true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
msg, merr := dnsExchange(ctx, "", parentServer, q, true)
|
||||
if merr != nil {
|
||||
return nil, nil, 0, false, merr
|
||||
}
|
||||
if msg.Rcode != dns.RcodeSuccess {
|
||||
return nil, nil, msg, fmt.Errorf("parent answered %s", dns.RcodeToString[msg.Rcode])
|
||||
return nil, nil, 0, false, fmt.Errorf("parent answered %s", dns.RcodeToString[msg.Rcode])
|
||||
}
|
||||
|
||||
glue = map[string][]string{}
|
||||
|
|
@ -127,6 +128,10 @@ func queryDelegation(ctx context.Context, parentServer, fqdn string) (ns []strin
|
|||
case *dns.NS:
|
||||
if strings.EqualFold(strings.TrimSuffix(t.Header().Name, "."), strings.TrimSuffix(fqdn, ".")) {
|
||||
ns = append(ns, strings.ToLower(dns.Fqdn(t.Ns)))
|
||||
if !nsTTLKnown {
|
||||
nsTTL = t.Header().Ttl
|
||||
nsTTLKnown = true
|
||||
}
|
||||
}
|
||||
case *dns.A:
|
||||
name := strings.ToLower(dns.Fqdn(t.Header().Name))
|
||||
|
|
@ -143,6 +148,20 @@ func queryDelegation(ctx context.Context, parentServer, fqdn string) (ns []strin
|
|||
return
|
||||
}
|
||||
|
||||
// queryCNAMETarget returns the CNAME target if host is an alias, or empty
|
||||
// string if it is not. Uses the system resolver, consistent with resolveHost.
|
||||
func queryCNAMETarget(ctx context.Context, host string) (string, error) {
|
||||
var resolver net.Resolver
|
||||
canon, err := resolver.LookupCNAME(ctx, strings.TrimSuffix(host, "."))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.EqualFold(dns.Fqdn(canon), dns.Fqdn(host)) {
|
||||
return "", nil
|
||||
}
|
||||
return strings.TrimSuffix(dns.Fqdn(canon), "."), nil
|
||||
}
|
||||
|
||||
// queryDS uses TCP because DS+RRSIG answers commonly exceed UDP MTU.
|
||||
func queryDS(ctx context.Context, parentServer, fqdn string) (ds []*dns.DS, sigs []*dns.RRSIG, err error) {
|
||||
q := dns.Question{Name: dns.Fqdn(fqdn), Qtype: dns.TypeDS, Qclass: dns.ClassINET}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue