checker-dnsviz/checker/types.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"`
}