Initial commit
This commit is contained in:
commit
2d98ed1b5d
33 changed files with 4644 additions and 0 deletions
144
checker/rules_soa.go
Normal file
144
checker/rules_soa.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// serialDriftRule flags disagreement between resolvers on the SOA serial.
|
||||
type serialDriftRule struct{}
|
||||
|
||||
func (r *serialDriftRule) Name() string { return "resolver_propagation.serial_drift" }
|
||||
func (r *serialDriftRule) Description() string {
|
||||
return "Flags disagreement on the SOA serial across unfiltered resolvers."
|
||||
}
|
||||
func (r *serialDriftRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
soaKey := rrsetKey(data.Zone, "SOA")
|
||||
if data.RRsets[soaKey] == nil {
|
||||
return []sdk.CheckState{{Status: sdk.StatusUnknown,
|
||||
Code: "resolver_propagation.serial_drift.skipped",
|
||||
Message: "SOA was not probed"}}
|
||||
}
|
||||
serials := map[uint32][]string{}
|
||||
for _, rv := range data.Resolvers {
|
||||
if rv.Filtered {
|
||||
continue
|
||||
}
|
||||
p := rv.Probes[soaKey]
|
||||
if p == nil || p.Error != "" || p.Rcode != "NOERROR" {
|
||||
continue
|
||||
}
|
||||
if s := extractSerial(p.Records); s != 0 {
|
||||
serials[s] = append(serials[s], rv.ID)
|
||||
}
|
||||
}
|
||||
if len(serials) < 2 {
|
||||
return []sdk.CheckState{passState("resolver_propagation.serial_drift.ok",
|
||||
"SOA serial is consistent across unfiltered resolvers.")}
|
||||
}
|
||||
var parts []string
|
||||
for s, rs := range serials {
|
||||
sort.Strings(rs)
|
||||
parts = append(parts, fmt.Sprintf("serial %d on %s", s, firstN(rs, 6)))
|
||||
}
|
||||
sort.Strings(parts)
|
||||
return []sdk.CheckState{warnState(CodeSerialDrift, soaKey,
|
||||
"SOA serial differs across resolvers, "+strings.Join(parts, "; "))}
|
||||
}
|
||||
|
||||
// staleCacheRule flags resolvers still serving a serial below the declared one.
|
||||
type staleCacheRule struct{}
|
||||
|
||||
func (r *staleCacheRule) Name() string { return "resolver_propagation.stale_cache" }
|
||||
func (r *staleCacheRule) Description() string {
|
||||
return "Flags resolvers still serving an SOA serial below the one saved by happyDomain."
|
||||
}
|
||||
func (r *staleCacheRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
if data.DeclaredSerial == 0 {
|
||||
return []sdk.CheckState{{Status: sdk.StatusUnknown,
|
||||
Code: "resolver_propagation.stale_cache.skipped",
|
||||
Message: "no declared SOA serial available for comparison"}}
|
||||
}
|
||||
soaKey := rrsetKey(data.Zone, "SOA")
|
||||
if data.RRsets[soaKey] == nil {
|
||||
return []sdk.CheckState{{Status: sdk.StatusUnknown,
|
||||
Code: "resolver_propagation.stale_cache.skipped",
|
||||
Message: "SOA was not probed"}}
|
||||
}
|
||||
var below []string
|
||||
for _, rv := range data.Resolvers {
|
||||
if rv.Filtered {
|
||||
continue
|
||||
}
|
||||
p := rv.Probes[soaKey]
|
||||
if p == nil || p.Error != "" || p.Rcode != "NOERROR" {
|
||||
continue
|
||||
}
|
||||
s := extractSerial(p.Records)
|
||||
if s != 0 && s < data.DeclaredSerial {
|
||||
below = append(below, rv.ID)
|
||||
}
|
||||
}
|
||||
if len(below) == 0 {
|
||||
return []sdk.CheckState{passState("resolver_propagation.stale_cache.ok",
|
||||
"No resolver is still serving an outdated SOA serial.")}
|
||||
}
|
||||
sort.Strings(below)
|
||||
return []sdk.CheckState{infoState(CodeStaleCache, soaKey,
|
||||
fmt.Sprintf("%d resolver(s) still return a serial below the declared one (%d): %s",
|
||||
len(below), data.DeclaredSerial, firstN(below, 6)))}
|
||||
}
|
||||
|
||||
// dnssecRule flags DNSSEC failures (SERVFAIL or missing AD) at the zone apex
|
||||
// on resolvers known to validate.
|
||||
type dnssecRule struct{}
|
||||
|
||||
func (r *dnssecRule) Name() string { return "resolver_propagation.dnssec" }
|
||||
func (r *dnssecRule) Description() string {
|
||||
return "Checks that validating resolvers successfully validate the zone's DNSSEC chain."
|
||||
}
|
||||
func (r *dnssecRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
soaKey := rrsetKey(data.Zone, "SOA")
|
||||
|
||||
var states []sdk.CheckState
|
||||
for _, rv := range data.Resolvers {
|
||||
if rv.Filtered || !isValidatingResolver(rv.ID) {
|
||||
continue
|
||||
}
|
||||
soa := rv.Probes[soaKey]
|
||||
if soa == nil || soa.Error != "" {
|
||||
continue
|
||||
}
|
||||
switch soa.Rcode {
|
||||
case "SERVFAIL":
|
||||
states = append(states, critState(CodeDNSSECFailure, rv.ID,
|
||||
fmt.Sprintf("%s returned SERVFAIL for %s, typically a broken DNSSEC chain", rv.Name, data.Zone)))
|
||||
case "NOERROR":
|
||||
if !soa.AD {
|
||||
states = append(states, infoState(CodeDNSSECUnvalidated, rv.ID,
|
||||
fmt.Sprintf("%s did not set AD=1 for %s, zone may not be DNSSEC-signed, or signature is broken", rv.Name, data.Zone)))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(states) == 0 {
|
||||
return []sdk.CheckState{passState("resolver_propagation.dnssec.ok",
|
||||
"Validating resolvers report no DNSSEC issue.")}
|
||||
}
|
||||
return states
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue