checker-authoritative-consi.../checker/rules_consistency_test.go

219 lines
6.6 KiB
Go

package checker
import (
"strings"
"testing"
"github.com/miekg/dns"
)
func mkSOA(serial uint32) *dns.SOA {
return &dns.SOA{
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeSOA},
Ns: "ns1.example.com.",
Mbox: "hostmaster.example.com.",
Serial: serial,
Refresh: 3600,
Retry: 600,
Expire: 86400,
Minttl: 300,
}
}
func TestCollectSerialDrift_NoDrift(t *testing.T) {
d := &ObservationData{
Probed: []string{"ns1.example.com.", "ns2.example.com."},
Results: map[string]*NSResult{
"ns1.example.com.": {Authoritative: true, SOA: mkSOA(10), Serial: 10},
"ns2.example.com.": {Authoritative: true, SOA: mkSOA(10), Serial: 10},
},
}
if got := collectSerialDrift(d); len(got) != 0 {
t.Errorf("expected no findings, got %v", got)
}
}
func TestCollectSerialDrift_Drift(t *testing.T) {
d := &ObservationData{
Probed: []string{"ns1.example.com.", "ns2.example.com.", "ns3.example.com."},
Results: map[string]*NSResult{
"ns1.example.com.": {Authoritative: true, SOA: mkSOA(10), Serial: 10},
"ns2.example.com.": {Authoritative: true, SOA: mkSOA(11), Serial: 11},
"ns3.example.com.": {Authoritative: false, SOA: mkSOA(99), Serial: 99}, // ignored
},
}
got := collectSerialDrift(d)
if len(got) != 1 || got[0].Code != CodeSerialDrift || got[0].Severity != SeverityCrit {
t.Fatalf("unexpected findings: %#v", got)
}
if !strings.Contains(got[0].Message, "serial 10") || !strings.Contains(got[0].Message, "serial 11") {
t.Errorf("message missing serials: %q", got[0].Message)
}
if strings.Contains(got[0].Message, "99") {
t.Errorf("non-authoritative server should not appear: %q", got[0].Message)
}
}
func TestCollectSerialVsSaved(t *testing.T) {
tests := []struct {
name string
saved uint32
nsSerials map[string]uint32
warn bool
wantCodes []string
wantSeverity []Severity
}{
{
name: "matches saved",
saved: 50,
nsSerials: map[string]uint32{"ns1.": 50, "ns2.": 50},
warn: true,
},
{
name: "saved newer than live -> stale",
saved: 50,
nsSerials: map[string]uint32{"ns1.": 49, "ns2.": 50},
warn: true,
wantCodes: []string{CodeSerialStaleVsSaved},
wantSeverity: []Severity{SeverityWarn},
},
{
name: "saved newer but warn disabled",
saved: 50,
nsSerials: map[string]uint32{"ns1.": 49},
warn: false,
},
{
name: "live ahead of saved -> info",
saved: 50,
nsSerials: map[string]uint32{"ns1.": 51},
warn: true,
wantCodes: []string{CodeSerialAheadOfSaved},
wantSeverity: []Severity{SeverityInfo},
},
{
name: "mixed",
saved: 50,
nsSerials: map[string]uint32{"ns1.": 49, "ns2.": 51},
warn: true,
wantCodes: []string{CodeSerialStaleVsSaved, CodeSerialAheadOfSaved},
wantSeverity: []Severity{SeverityWarn, SeverityInfo},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &ObservationData{DeclaredSerial: tt.saved, Results: map[string]*NSResult{}}
for ns, s := range tt.nsSerials {
d.Probed = append(d.Probed, ns)
d.Results[ns] = &NSResult{Authoritative: true, SOA: mkSOA(s), Serial: s}
}
got := collectSerialVsSaved(d, tt.warn)
if len(got) != len(tt.wantCodes) {
t.Fatalf("got %d findings, want %d: %#v", len(got), len(tt.wantCodes), got)
}
codes := map[string]Severity{}
for _, f := range got {
codes[f.Code] = f.Severity
}
for i, c := range tt.wantCodes {
if sev, ok := codes[c]; !ok || sev != tt.wantSeverity[i] {
t.Errorf("missing or wrong-severity %s: got %v", c, codes)
}
}
})
}
}
func TestCollectSOAFieldsDrift(t *testing.T) {
soaA := mkSOA(10)
soaB := mkSOA(10)
soaB.Refresh = 9999 // different RDATA
soaC := mkSOA(11) // same RDATA as A but different serial; should NOT trigger this rule
d := &ObservationData{
Probed: []string{"ns1.", "ns2.", "ns3."},
Results: map[string]*NSResult{
"ns1.": {SOA: soaA},
"ns2.": {SOA: soaB},
"ns3.": {SOA: soaC},
},
}
got := collectSOAFieldsDrift(d)
if len(got) != 1 || got[0].Code != CodeSOAFieldsDrift {
t.Fatalf("expected one SOAFieldsDrift finding, got %#v", got)
}
// Two distinct RDATA buckets (A+C grouped, B alone).
if !strings.Contains(got[0].Message, "refresh=3600") || !strings.Contains(got[0].Message, "refresh=9999") {
t.Errorf("message missing refresh values: %q", got[0].Message)
}
}
func TestCollectSOAFieldsDrift_NoDriftWhenOnlySerialDiffers(t *testing.T) {
d := &ObservationData{
Probed: []string{"ns1.", "ns2."},
Results: map[string]*NSResult{
"ns1.": {SOA: mkSOA(10)},
"ns2.": {SOA: mkSOA(11)},
},
}
if got := collectSOAFieldsDrift(d); len(got) != 0 {
t.Errorf("serial-only difference should not be flagged here: %v", got)
}
}
func TestCollectNSRRsetDrift_Consistent(t *testing.T) {
rrset := []string{"ns1.example.com.", "ns2.example.com."}
d := &ObservationData{
Probed: []string{"ns1.example.com.", "ns2.example.com."},
DeclaredNS: rrset,
Results: map[string]*NSResult{
"ns1.example.com.": {Authoritative: true, NSRRset: rrset},
"ns2.example.com.": {Authoritative: true, NSRRset: rrset},
},
}
if got := collectNSRRsetDrift(d); len(got) != 0 {
t.Errorf("expected no findings, got %v", got)
}
}
func TestCollectNSRRsetDrift_Drift(t *testing.T) {
d := &ObservationData{
Probed: []string{"ns1.example.com.", "ns2.example.com."},
DeclaredNS: []string{"ns1.example.com.", "ns2.example.com."},
Results: map[string]*NSResult{
"ns1.example.com.": {Authoritative: true, NSRRset: []string{"ns1.example.com.", "ns2.example.com."}},
"ns2.example.com.": {Authoritative: true, NSRRset: []string{"ns1.example.com."}},
},
}
got := collectNSRRsetDrift(d)
codes := map[string]bool{}
for _, f := range got {
codes[f.Code] = true
}
if !codes[CodeNSRRsetDrift] {
t.Errorf("expected NSRRsetDrift, got %v", codes)
}
}
func TestCollectNSRRsetDrift_MismatchConfig(t *testing.T) {
d := &ObservationData{
Probed: []string{"ns1.example.com."},
DeclaredNS: []string{"ns1.example.com.", "ns2.example.com."},
Results: map[string]*NSResult{
"ns1.example.com.": {Authoritative: true, NSRRset: []string{"ns1.example.com.", "ns3.example.com."}},
},
}
got := collectNSRRsetDrift(d)
var found bool
for _, f := range got {
if f.Code == CodeNSRRsetMismatchConfig {
found = true
if !strings.Contains(f.Message, "ns2.example.com") || !strings.Contains(f.Message, "ns3.example.com") {
t.Errorf("message missing missing/extra entries: %q", f.Message)
}
}
}
if !found {
t.Errorf("expected NSRRsetMismatchConfig in %v", got)
}
}