86 lines
4 KiB
Go
86 lines
4 KiB
Go
// SPDX-License-Identifier: MIT
|
|
|
|
// Package checker implements a happyDomain checker that wraps DNSViz
|
|
// (https://github.com/dnsviz/dnsviz). It runs `dnsviz probe` followed by
|
|
// `dnsviz grok` against a domain, stores the structured analysis as the
|
|
// observation, and turns the per-zone errors/warnings into CheckStates.
|
|
//
|
|
// The container ships dnsviz alongside this binary, so the checker has no
|
|
// external dependency at runtime besides the network.
|
|
package checker
|
|
|
|
import (
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// ObservationKeyDNSViz is the observation key for DNSViz analysis output.
|
|
const ObservationKeyDNSViz sdk.ObservationKey = "dnsviz"
|
|
|
|
// DNSVizData is what Collect stores. It carries the full grok output
|
|
// (parsed into a permissive structure) plus the raw bytes for the report.
|
|
//
|
|
// DNSViz emits a single top-level object whose keys are zone FQDNs (with
|
|
// trailing dot), one per level of the chain. Inside each zone object the
|
|
// shape is permissive: many fields are conditional, so we keep most of them
|
|
// as map[string]any and only pluck out what the rules need.
|
|
type DNSVizData struct {
|
|
// Domain is the queried FQDN, with trailing dot stripped.
|
|
Domain string `json:"domain"`
|
|
|
|
// Zones is the per-zone analysis, keyed by zone FQDN (with trailing dot
|
|
// preserved, matching DNSViz's output).
|
|
Zones map[string]ZoneAnalysis `json:"zones"`
|
|
|
|
// Order is Zones' keys, sorted from the queried name up to the root.
|
|
// We surface it explicitly so the report can render in a stable order
|
|
// without having to re-sort on every render.
|
|
Order []string `json:"order,omitempty"`
|
|
|
|
// Raw is the unmodified `dnsviz grok` JSON. Kept around so the report
|
|
// can fall back on it for fields the typed view does not capture.
|
|
Raw []byte `json:"raw,omitempty"`
|
|
|
|
// ProbeStderr / GrokStderr capture the diagnostics dnsviz prints to
|
|
// stderr. Useful when collection succeeds but the analysis is partial.
|
|
ProbeStderr string `json:"probe_stderr,omitempty"`
|
|
GrokStderr string `json:"grok_stderr,omitempty"`
|
|
}
|
|
|
|
// ZoneAnalysis is a permissive view over one zone's grok block.
|
|
//
|
|
// DNSViz output puts the DNSSEC chain status at delegation.status (one of
|
|
// "SECURE", "BOGUS", "INSECURE", "INDETERMINATE") while the top-level
|
|
// "status" field carries the DNS rcode for the zone apex (e.g. "NOERROR").
|
|
// Errors and warnings are not surfaced as a flat per-zone array; instead
|
|
// they appear as nested "errors"/"warnings" arrays attached to the record
|
|
// where the problem was found (DS, DNSKEY, RRSIG, NSEC proof, query
|
|
// response, server, …). We walk the whole zone subtree to collect them.
|
|
type ZoneAnalysis struct {
|
|
// Status is the DNSSEC chain status taken from delegation.status when
|
|
// available, falling back to the top-level "status" field otherwise.
|
|
Status string `json:"status,omitempty"`
|
|
// DNSStatus is the raw top-level "status" field (DNS rcode such as
|
|
// "NOERROR"). Kept for the report so we can distinguish "DNS resolved
|
|
// fine" from "DNSSEC chain validates".
|
|
DNSStatus string `json:"dns_status,omitempty"`
|
|
Errors []Finding `json:"errors,omitempty"`
|
|
Warnings []Finding `json:"warnings,omitempty"`
|
|
}
|
|
|
|
// Finding mirrors the shape DNSViz uses for entries in errors/warnings.
|
|
// Producers occasionally use slightly different field names across versions
|
|
// of dnsviz; we accept both `description`/`message` for the human text and
|
|
// fall back to a generic stringification at parse time.
|
|
type Finding struct {
|
|
Code string `json:"code,omitempty"`
|
|
Description string `json:"description"`
|
|
Servers []string `json:"servers,omitempty"`
|
|
// Path is a slash-separated pointer to the JSON node where the finding
|
|
// was attached (e.g. "delegation/ds[0]" or
|
|
// "queries/example.com./IN/A/answer[0]/rrsig[0]"). Useful in the
|
|
// report so a generic "signature_invalid" can be located precisely.
|
|
Path string `json:"path,omitempty"`
|
|
// Extra holds the raw finding payload when no human description could
|
|
// be extracted. Surfaced by the report as a debug fallback.
|
|
Extra map[string]any `json:"extra,omitempty"`
|
|
}
|