219 lines
6.6 KiB
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)
|
|
}
|
|
}
|