checker-authoritative-consi.../checker/rules_per_ns.go

209 lines
6.5 KiB
Go

package checker
import (
"context"
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
)
type nsResolvableRule struct{}
func (r *nsResolvableRule) Name() string { return "authoritative_consistency.ns_resolvable" }
func (r *nsResolvableRule) Description() string {
return "Verifies that every authoritative name server hostname resolves to at least one A or AAAA address."
}
func (r *nsResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
data, errSt := loadObservation(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
var findings []Finding
for _, ns := range data.Probed {
res := data.Results[ns]
if res == nil {
continue
}
if res.ResolveError != "" {
findings = append(findings, Finding{
Code: CodeNSUnresolvable,
Severity: SeverityCrit,
Message: fmt.Sprintf("cannot resolve %s: %s", ns, res.ResolveError),
Server: ns,
})
}
}
if len(findings) == 0 {
return []sdk.CheckState{passState("authoritative_consistency.ns_resolvable.ok", "Every probed name server resolves to at least one address.")}
}
return findingsToStates(findings)
}
type nsReachableRule struct{}
func (r *nsReachableRule) Name() string { return "authoritative_consistency.ns_reachable" }
func (r *nsReachableRule) Description() string {
return "Verifies that every authoritative name server answers over UDP/53 and TCP/53."
}
func (r *nsReachableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
data, errSt := loadObservation(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
requireTCP := sdk.GetBoolOption(opts, "requireTCP", true)
var findings []Finding
for _, ns := range data.Probed {
res := data.Results[ns]
if res == nil || res.ResolveError != "" {
continue
}
if !res.UDPReachable {
findings = append(findings, Finding{
Code: CodeNSUDPFailed,
Severity: SeverityCrit,
Message: fmt.Sprintf("%s did not answer any SOA query over UDP/53", ns),
Server: ns,
})
continue
}
if !res.TCPReachable {
sev := SeverityWarn
msg := fmt.Sprintf("%s did not answer over TCP/53", ns)
if requireTCP {
sev = SeverityCrit
msg = fmt.Sprintf("%s did not answer over TCP/53 (required by RFC 7766 and DNSSEC)", ns)
}
findings = append(findings, Finding{
Code: CodeNSTCPFailed,
Severity: sev,
Message: msg,
Server: ns,
})
}
}
if len(findings) == 0 {
return []sdk.CheckState{passState("authoritative_consistency.ns_reachable.ok", "Every probed name server is reachable over UDP/53 and TCP/53.")}
}
return findingsToStates(findings)
}
type authoritativeRule struct{}
func (r *authoritativeRule) Name() string { return "authoritative_consistency.authoritative" }
func (r *authoritativeRule) Description() string {
return "Verifies that every reachable name server is authoritative for the zone (no lame delegation) and returns a SOA."
}
func (r *authoritativeRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
data, errSt := loadObservation(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
var findings []Finding
for _, ns := range data.Probed {
res := data.Results[ns]
if res == nil || !res.UDPReachable {
continue
}
if !res.Authoritative {
findings = append(findings, Finding{
Code: CodeLame,
Severity: SeverityCrit,
Message: fmt.Sprintf("%s is not authoritative for %s (lame delegation)", ns, data.Zone),
Server: ns,
})
continue
}
if data.HasSOA && res.SOA == nil {
findings = append(findings, Finding{
Code: CodeNoSOA,
Severity: SeverityCrit,
Message: fmt.Sprintf("%s is authoritative but returned no SOA for %s", ns, data.Zone),
Server: ns,
})
}
}
if len(findings) == 0 {
return []sdk.CheckState{passState("authoritative_consistency.authoritative.ok", "Every reachable name server is authoritative for the zone.")}
}
return findingsToStates(findings)
}
type ednsRule struct{}
func (r *ednsRule) Name() string { return "authoritative_consistency.edns" }
func (r *ednsRule) Description() string {
return "Verifies that every reachable name server correctly handles EDNS0 queries (required by DNSSEC and for large answers)."
}
func (r *ednsRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
if !sdk.GetBoolOption(opts, "checkEDNS", true) {
return []sdk.CheckState{notTestedState("authoritative_consistency.edns.skipped", "EDNS0 check disabled by option.")}
}
data, errSt := loadObservation(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
var findings []Finding
for _, ns := range data.Probed {
res := data.Results[ns]
if res == nil || !res.UDPReachable {
continue
}
if !res.EDNSSupported {
findings = append(findings, Finding{
Code: CodeEDNSUnsupported,
Severity: SeverityWarn,
Message: fmt.Sprintf("%s does not correctly handle EDNS0 (breaks DNSSEC and large answers)", ns),
Server: ns,
})
}
}
if len(findings) == 0 {
return []sdk.CheckState{passState("authoritative_consistency.edns.ok", "Every reachable name server handles EDNS0 correctly.")}
}
return findingsToStates(findings)
}
type latencyRule struct{}
func (r *latencyRule) Name() string { return "authoritative_consistency.latency" }
func (r *latencyRule) Description() string {
return "Flags authoritative name servers whose response latency exceeds the configured threshold."
}
func (r *latencyRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
if !sdk.GetBoolOption(opts, "checkLatency", true) {
return []sdk.CheckState{notTestedState("authoritative_consistency.latency.skipped", "Latency check disabled by option.")}
}
data, errSt := loadObservation(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
threshold := int64(sdk.GetIntOption(opts, "latencyThresholdMs", 500))
var findings []Finding
for _, ns := range data.Probed {
res := data.Results[ns]
if res == nil || !res.UDPReachable {
continue
}
if res.LatencyMs > threshold {
findings = append(findings, Finding{
Code: CodeSlowNS,
Severity: SeverityInfo,
Message: fmt.Sprintf("%s responded in %d ms (above %d ms threshold)", ns, res.LatencyMs, threshold),
Server: ns,
})
}
}
if len(findings) == 0 {
return []sdk.CheckState{passState("authoritative_consistency.latency.ok", "Every reachable name server responded within the configured threshold.")}
}
return findingsToStates(findings)
}