|
|
@@ -26,18 +26,22 @@ import (
|
|
|
|
|
|
|
|
|
|
|
|
// The watcher looks these names up in live DNS with the real resolver,
|
|
|
|
// The watcher looks these names up in live DNS with the real resolver,
|
|
|
|
// so tests assert on what the watcher does with the answers, never on
|
|
|
|
// so tests assert on what the watcher does with the answers, never on
|
|
|
|
// the records these zones publish. testHost's nameservers and addresses
|
|
|
|
// the records these zones publish. The nameservers of testHost and
|
|
|
|
// stay the same from one check to the next, which the tests that check
|
|
|
|
// testSmallDomain stay the same between a test looking them up and its
|
|
|
|
// it twice rely on, and testSmallDomain's nameservers stay the same
|
|
|
|
// check. Every query a check sends is one more that can be lost, so the
|
|
|
|
// between a test looking them up and its check. A domain check looks up
|
|
|
|
// tests keep them few. A check asks each of a name's nameservers about
|
|
|
|
// each nameserver's addresses, about a second per nameserver, so the
|
|
|
|
// every record type, and both names have two. A domain check also looks
|
|
|
|
// tests that check a domain use testSmallDomain, which has two
|
|
|
|
// up each nameserver's addresses at every nameserver of the zone that
|
|
|
|
// nameservers, and check it once. The tests that query testDomain's
|
|
|
|
// nameserver is in: testSmallDomain's nameservers are in zones with two
|
|
|
|
// nameservers directly do no domain check.
|
|
|
|
// nameservers, while a domain whose nameservers are in, say,
|
|
|
|
|
|
|
|
// cloudflare.com, which has five, makes each domain check much longer.
|
|
|
|
|
|
|
|
// A test checks a domain only when it is about domains, and checks once,
|
|
|
|
|
|
|
|
// from saved state it builds, rather than twice. The tests that query
|
|
|
|
|
|
|
|
// testDomain's nameservers directly do no domain check.
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
testDomain = "google.com"
|
|
|
|
testDomain = "google.com"
|
|
|
|
testSmallDomain = "example.com"
|
|
|
|
testSmallDomain = "desec.io"
|
|
|
|
testHost = "cloudflare.com"
|
|
|
|
testHost = "example.org"
|
|
|
|
testIssuer = "DigiCert"
|
|
|
|
testIssuer = "DigiCert"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
@@ -259,55 +263,48 @@ func checkOnce(
|
|
|
|
|
|
|
|
|
|
|
|
// runChecks builds a watcher, lets prepare set up the saved state and
|
|
|
|
// runChecks builds a watcher, lets prepare set up the saved state and
|
|
|
|
// stand-ins it starts from, and runs its checks once against live DNS.
|
|
|
|
// stand-ins it starts from, and runs its checks once against live DNS.
|
|
|
|
// If change is not nil, change then alters the saved state or stand-ins
|
|
|
|
// When the check finds no fresh address for a name (see checkOnce), the
|
|
|
|
// and the checks run a second time. When either check finds no fresh
|
|
|
|
// watcher is thrown away and all of this runs again on a new one, so a
|
|
|
|
// address for a name (see checkOnce), the watcher is thrown away and
|
|
|
|
// failed attempt leaves nothing behind in the saved state, the
|
|
|
|
// all of this runs again on a new one, so a failed attempt leaves
|
|
|
|
// stand-ins or the notifications.
|
|
|
|
// nothing behind in the saved state, the stand-ins or the notifications.
|
|
|
|
|
|
|
|
func runChecks(
|
|
|
|
func runChecks(
|
|
|
|
t *testing.T,
|
|
|
|
t *testing.T,
|
|
|
|
cfg *config.Config,
|
|
|
|
cfg *config.Config,
|
|
|
|
prepare, change func(deps *testDeps),
|
|
|
|
prepare func(deps *testDeps),
|
|
|
|
) *testDeps {
|
|
|
|
) (*watcher.Watcher, *testDeps) {
|
|
|
|
t.Helper()
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
|
|
var deps *testDeps
|
|
|
|
var (
|
|
|
|
|
|
|
|
w *watcher.Watcher
|
|
|
|
|
|
|
|
deps *testDeps
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
livednstest.Retry(t, "watcher checks", func(ctx context.Context) error {
|
|
|
|
livednstest.Retry(t, "watcher checks", func(ctx context.Context) error {
|
|
|
|
var w *watcher.Watcher
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w, deps = newTestWatcher(t, cfg)
|
|
|
|
w, deps = newTestWatcher(t, cfg)
|
|
|
|
|
|
|
|
|
|
|
|
if prepare != nil {
|
|
|
|
if prepare != nil {
|
|
|
|
prepare(deps)
|
|
|
|
prepare(deps)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err := checkOnce(ctx, w, deps)
|
|
|
|
|
|
|
|
if err != nil || change == nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
change(deps)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return checkOnce(ctx, w, deps)
|
|
|
|
return checkOnce(ctx, w, deps)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return deps
|
|
|
|
return w, deps
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// lookupNameservers returns the nameservers live DNS lists for domain,
|
|
|
|
// lookupNameservers returns the nameservers live DNS lists for name,
|
|
|
|
// for a test to save in the state its check starts from.
|
|
|
|
// for a test to save in the state its check starts from.
|
|
|
|
func lookupNameservers(t *testing.T, domain string) []string {
|
|
|
|
func lookupNameservers(t *testing.T, name string) []string {
|
|
|
|
t.Helper()
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
|
|
res := resolver.NewFromLogger(slog.Default())
|
|
|
|
res := resolver.NewFromLogger(slog.Default())
|
|
|
|
|
|
|
|
|
|
|
|
var nameservers []string
|
|
|
|
var nameservers []string
|
|
|
|
|
|
|
|
|
|
|
|
livednstest.Retry(t, "LookupNS("+domain+")", func(ctx context.Context) error {
|
|
|
|
livednstest.Retry(t, "LookupNS("+name+")", func(ctx context.Context) error {
|
|
|
|
var err error
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
|
|
nameservers, err = res.LookupNS(ctx, domain)
|
|
|
|
nameservers, err = res.LookupNS(ctx, name)
|
|
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
})
|
|
|
@@ -370,7 +367,7 @@ func TestFirstRunBaseline(t *testing.T) {
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
deps := runChecks(t, cfg, nil, nil)
|
|
|
|
_, deps := runChecks(t, cfg, nil)
|
|
|
|
|
|
|
|
|
|
|
|
assertNoNotifications(t, deps)
|
|
|
|
assertNoNotifications(t, deps)
|
|
|
|
assertStatePopulated(t, deps)
|
|
|
|
assertStatePopulated(t, deps)
|
|
|
@@ -422,7 +419,7 @@ func TestDomainPortAndTLSChecks(t *testing.T) {
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
|
|
|
|
|
|
|
|
deps := runChecks(t, cfg, nil, nil)
|
|
|
|
_, deps := runChecks(t, cfg, nil)
|
|
|
|
|
|
|
|
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
|
|
|
|
|
|
|
@@ -462,11 +459,11 @@ func TestNSChangeDetection(t *testing.T) {
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
cfg.Domains = []string{testSmallDomain}
|
|
|
|
|
|
|
|
|
|
|
|
// The saved state lists nameservers that live DNS does not.
|
|
|
|
// The saved state lists nameservers that live DNS does not.
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
deps.state.SetDomainState(testSmallDomain, &state.DomainState{
|
|
|
|
deps.state.SetDomainState(testSmallDomain, &state.DomainState{
|
|
|
|
Nameservers: []string{oldNS1, oldNS2},
|
|
|
|
Nameservers: []string{oldNS1, oldNS2},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, nil)
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "NS Change: "+testSmallDomain, "warning")
|
|
|
|
assertNotified(t, deps, "NS Change: "+testSmallDomain, "warning")
|
|
|
|
|
|
|
|
|
|
|
@@ -486,7 +483,7 @@ func TestNSAddressChangeDetection(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
|
|
// The saved state lists the nameservers live DNS lists, each at an
|
|
|
|
// The saved state lists the nameservers live DNS lists, each at an
|
|
|
|
// address live DNS never returns.
|
|
|
|
// address live DNS never returns.
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
nsAddresses := make(map[string][]string, len(nameservers))
|
|
|
|
nsAddresses := make(map[string][]string, len(nameservers))
|
|
|
|
for _, ns := range nameservers {
|
|
|
|
for _, ns := range nameservers {
|
|
|
|
nsAddresses[ns] = []string{oldIP}
|
|
|
|
nsAddresses[ns] = []string{oldIP}
|
|
|
@@ -496,7 +493,7 @@ func TestNSAddressChangeDetection(t *testing.T) {
|
|
|
|
Nameservers: nameservers,
|
|
|
|
Nameservers: nameservers,
|
|
|
|
NameserverAddresses: nsAddresses,
|
|
|
|
NameserverAddresses: nsAddresses,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, nil)
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
title := "NS Address Change: " + testSmallDomain
|
|
|
|
title := "NS Address Change: " + testSmallDomain
|
|
|
|
ds, _ := deps.state.GetDomainState(testSmallDomain)
|
|
|
|
ds, _ := deps.state.GetDomainState(testSmallDomain)
|
|
|
@@ -542,12 +539,12 @@ func TestNSAddedAndRemovedIsNoAddressChange(t *testing.T) {
|
|
|
|
// The saved state lists oldNS1, which live DNS does not, in place of
|
|
|
|
// The saved state lists oldNS1, which live DNS does not, in place of
|
|
|
|
// the first nameserver live DNS lists, so that the check finds that
|
|
|
|
// the first nameserver live DNS lists, so that the check finds that
|
|
|
|
// one added and oldNS1 removed. Only oldNS1 has addresses saved.
|
|
|
|
// one added and oldNS1 removed. Only oldNS1 has addresses saved.
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
deps.state.SetDomainState(testSmallDomain, &state.DomainState{
|
|
|
|
deps.state.SetDomainState(testSmallDomain, &state.DomainState{
|
|
|
|
Nameservers: append([]string{oldNS1}, nameservers[1:]...),
|
|
|
|
Nameservers: append([]string{oldNS1}, nameservers[1:]...),
|
|
|
|
NameserverAddresses: map[string][]string{oldNS1: {oldIP}},
|
|
|
|
NameserverAddresses: map[string][]string{oldNS1: {oldIP}},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, nil)
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if n := countNotifications(deps, "NS Change: "+testSmallDomain); n != 1 {
|
|
|
|
if n := countNotifications(deps, "NS Change: "+testSmallDomain); n != 1 {
|
|
|
|
t.Errorf("sent %d NS changes, want 1", n)
|
|
|
|
t.Errorf("sent %d NS changes, want 1", n)
|
|
|
@@ -565,15 +562,17 @@ func TestRecordChangeDetection(t *testing.T) {
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
// Between the checks, save for every nameserver an address live DNS
|
|
|
|
nameservers := lookupNameservers(t, testHost)
|
|
|
|
// never returns.
|
|
|
|
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
// The saved state has every nameserver live DNS lists answering
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
// with an address live DNS never returns.
|
|
|
|
for _, nsState := range hs.RecordsByNameserver {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
nsState.Records = map[string][]string{"A": {oldIP}}
|
|
|
|
byNameserver := make(map[string]*state.NameserverRecordState)
|
|
|
|
|
|
|
|
for _, ns := range nameservers {
|
|
|
|
|
|
|
|
byNameserver[ns] = answered(map[string][]string{"A": {oldIP}})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
deps.state.SetHostnameState(testHost, saved(byNameserver))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "Record Change: "+testHost, "warning")
|
|
|
|
assertNotified(t, deps, "Record Change: "+testHost, "warning")
|
|
|
@@ -585,12 +584,15 @@ func TestPortStateChange(t *testing.T) {
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
// Between the checks, every port closes.
|
|
|
|
w, deps := runChecks(t, cfg, nil)
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
|
|
|
|
|
|
|
|
// Every port closes, and the port checks run again. They look
|
|
|
|
|
|
|
|
// nothing up.
|
|
|
|
deps.portChecker.mu.Lock()
|
|
|
|
deps.portChecker.mu.Lock()
|
|
|
|
deps.portChecker.closed = true
|
|
|
|
deps.portChecker.closed = true
|
|
|
|
deps.portChecker.mu.Unlock()
|
|
|
|
deps.portChecker.mu.Unlock()
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
w.CheckAllPorts(t.Context())
|
|
|
|
|
|
|
|
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
assertNotified(
|
|
|
|
assertNotified(
|
|
|
@@ -610,7 +612,7 @@ func TestTLSExpiryWarning(t *testing.T) {
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
deps := runChecks(t, cfg, expiresInThreeDays, nil)
|
|
|
|
_, deps := runChecks(t, cfg, expiresInThreeDays)
|
|
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "TLS Expiry Warning: "+testHost, "warning")
|
|
|
|
assertNotified(t, deps, "TLS Expiry Warning: "+testHost, "warning")
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -782,7 +784,7 @@ func TestDNSRunsBeforePortAndTLSChecks(t *testing.T) {
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
// The saved state says the last check found testHost at oldIP.
|
|
|
|
// The saved state says the last check found testHost at oldIP.
|
|
|
|
deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
deps.state.SetHostnameState(testHost, &state.HostnameState{
|
|
|
|
deps.state.SetHostnameState(testHost, &state.HostnameState{
|
|
|
|
RecordsByNameserver: map[string]*state.NameserverRecordState{
|
|
|
|
RecordsByNameserver: map[string]*state.NameserverRecordState{
|
|
|
|
oldNS1: {
|
|
|
|
oldNS1: {
|
|
|
@@ -791,7 +793,7 @@ func TestDNSRunsBeforePortAndTLSChecks(t *testing.T) {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, nil)
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
snap := deps.state.GetSnapshot()
|
|
|
|
|
|
|
|
|
|
|
@@ -922,21 +924,20 @@ func TestNSFailureAndRecovery(t *testing.T) {
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg := defaultTestConfig(t)
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
cfg.Hostnames = []string{testHost}
|
|
|
|
|
|
|
|
|
|
|
|
// Between the checks, save every nameserver the first check found
|
|
|
|
nameservers := lookupNameservers(t, testHost)
|
|
|
|
// as one that did not answer, and add, as answering, one that live
|
|
|
|
|
|
|
|
// DNS does not list, which then disappears.
|
|
|
|
// The saved state has every nameserver live DNS lists as one that
|
|
|
|
deps := runChecks(t, cfg, nil, func(deps *testDeps) {
|
|
|
|
// did not answer, and, as answering, one that live DNS does not
|
|
|
|
hs, _ := deps.state.GetHostnameState(testHost)
|
|
|
|
// list, which then disappears.
|
|
|
|
for ns := range hs.RecordsByNameserver {
|
|
|
|
_, deps := runChecks(t, cfg, func(deps *testDeps) {
|
|
|
|
hs.RecordsByNameserver[ns] = failed()
|
|
|
|
byNameserver := map[string]*state.NameserverRecordState{
|
|
|
|
|
|
|
|
oldNS1: answered(map[string][]string{"A": {oldIP}}),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ns := range nameservers {
|
|
|
|
|
|
|
|
byNameserver[ns] = failed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hs.RecordsByNameserver[oldNS1] = &state.NameserverRecordState{
|
|
|
|
deps.state.SetHostnameState(testHost, saved(byNameserver))
|
|
|
|
Records: map[string][]string{"A": {oldIP}},
|
|
|
|
|
|
|
|
Status: "ok",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deps.state.SetHostnameState(testHost, hs)
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assertNotified(t, deps, "NS Failure: "+testHost, "error")
|
|
|
|
assertNotified(t, deps, "NS Failure: "+testHost, "error")
|
|
|
|