Compare commits
No commits in common. "master" and "v0.1.0" have entirely different histories.
9 changed files with 83 additions and 328 deletions
|
|
@ -1,22 +0,0 @@
|
||||||
image: happydomain/checker-dnsviz:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: happydomain/checker-dnsviz:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: happydomain/checker-dnsviz:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: happydomain/checker-dnsviz:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
187
.drone.yml
187
.drone.yml
|
|
@ -1,187 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-amd64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: checker build
|
|
||||||
image: golang:1-alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git make
|
|
||||||
- make
|
|
||||||
environment:
|
|
||||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: checker build tag
|
|
||||||
image: golang:1-alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git make
|
|
||||||
- make
|
|
||||||
environment:
|
|
||||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: publish on Docker Hub
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: happydomain/checker-dnsviz
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
build_args:
|
|
||||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: publish on Docker Hub (tag)
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: happydomain/checker-dnsviz
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
build_args:
|
|
||||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- renovate/*
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-arm64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: checker build
|
|
||||||
image: golang:1-alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git make
|
|
||||||
- make
|
|
||||||
environment:
|
|
||||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: checker build tag
|
|
||||||
image: golang:1-alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git make
|
|
||||||
- make
|
|
||||||
environment:
|
|
||||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: publish on Docker Hub
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: happydomain/checker-dnsviz
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
build_args:
|
|
||||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: publish on Docker Hub (tag)
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: happydomain/checker-dnsviz
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
build_args:
|
|
||||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: docker-manifest
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: publish on Docker Hub
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- renovate/*
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build-amd64
|
|
||||||
- build-arm64
|
|
||||||
|
|
@ -59,12 +59,18 @@ func decodeZone(raw json.RawMessage) ZoneAnalysis {
|
||||||
z.Status = s
|
z.Status = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store raw queries so rules can infer the zone status from RRSIG
|
// Root has no parent and therefore no delegation block. dnsviz signals
|
||||||
// validation when no delegation block is present (e.g. root zone).
|
// trust-anchor validation through the RRSIG covering the apex DNSKEY
|
||||||
// Status inference and DNS-rcode fallback are the responsibility of the
|
// rrset (queries.<zone>/IN/DNSKEY.answer[*].rrsig[*].status). With
|
||||||
// evaluation layer (see effectiveStatus in rule.go).
|
// `dnsviz grok -t …` and a matching anchor, that RRSIG becomes VALID
|
||||||
if q, ok := m["queries"].(map[string]any); ok {
|
// and we lift the zone to SECURE; an INVALID/EXPIRED RRSIG drags it
|
||||||
z.Queries = q
|
// to BOGUS. Without a trust anchor, this leaves Status empty and we
|
||||||
|
// fall back to the DNS rcode below.
|
||||||
|
if z.Status == "" {
|
||||||
|
z.Status = inferApexDNSKEYStatus(m["queries"])
|
||||||
|
}
|
||||||
|
if z.Status == "" {
|
||||||
|
z.Status = z.DNSStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
z.Errors, z.Warnings = collectFindings(m, "")
|
z.Errors, z.Warnings = collectFindings(m, "")
|
||||||
|
|
@ -175,6 +181,62 @@ func makeFinding(item any, codeHint, path string) Finding {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inferApexDNSKEYStatus returns "SECURE", "BOGUS", or "" based on the
|
||||||
|
// status of RRSIGs covering the zone's apex DNSKEY rrset. dnsviz attaches
|
||||||
|
// a per-RRSIG status whenever a key reaches it (either through DS from
|
||||||
|
// the parent or through a configured trust anchor at this zone). For
|
||||||
|
// the root, this is the only place where trust-anchor validation
|
||||||
|
// surfaces in the grok output.
|
||||||
|
//
|
||||||
|
// queries is the value at zone["queries"], a map keyed by
|
||||||
|
// "<zone>/IN/<RRTYPE>". We pick the DNSKEY query and look at every
|
||||||
|
// RRSIG inside its answer.
|
||||||
|
func inferApexDNSKEYStatus(queries any) string {
|
||||||
|
q, ok := queries.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var dnskeyQ map[string]any
|
||||||
|
for k, v := range q {
|
||||||
|
if !strings.HasSuffix(k, "/IN/DNSKEY") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m, ok := v.(map[string]any); ok {
|
||||||
|
dnskeyQ = m
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dnskeyQ == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
answers, _ := dnskeyQ["answer"].([]any)
|
||||||
|
sawValid := false
|
||||||
|
for _, a := range answers {
|
||||||
|
am, _ := a.(map[string]any)
|
||||||
|
if am == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rrsigs, _ := am["rrsig"].([]any)
|
||||||
|
for _, rs := range rrsigs {
|
||||||
|
rm, _ := rs.(map[string]any)
|
||||||
|
if rm == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, _ := rm["status"].(string)
|
||||||
|
switch strings.ToUpper(s) {
|
||||||
|
case "INVALID", "BOGUS", "EXPIRED", "PREMATURE":
|
||||||
|
return "BOGUS"
|
||||||
|
case "VALID", "SECURE":
|
||||||
|
sawValid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sawValid {
|
||||||
|
return "SECURE"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func labelDepth(zone string) int {
|
func labelDepth(zone string) int {
|
||||||
z := strings.TrimSuffix(zone, ".")
|
z := strings.TrimSuffix(zone, ".")
|
||||||
if z == "" {
|
if z == "" {
|
||||||
|
|
|
||||||
|
|
@ -99,19 +99,11 @@ func TestParseGrokOutput_StringZone(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeZone_StatusFallbacks(t *testing.T) {
|
func TestDecodeZone_StatusFallbacks(t *testing.T) {
|
||||||
// Only top-level status; no delegation block.
|
// Only top-level status; no delegation block. Status must fall back to it.
|
||||||
// The observation layer stores DNSStatus and leaves Status empty.
|
|
||||||
// effectiveStatus (rule layer) is responsible for the DNS-rcode fallback.
|
|
||||||
raw := []byte(`{"status": "NOERROR"}`)
|
raw := []byte(`{"status": "NOERROR"}`)
|
||||||
z := decodeZone(raw)
|
z := decodeZone(raw)
|
||||||
if z.DNSStatus != "NOERROR" {
|
if z.DNSStatus != "NOERROR" || z.Status != "NOERROR" {
|
||||||
t.Errorf("expected DNSStatus = NOERROR, got %q", z.DNSStatus)
|
t.Errorf("expected Status and DNSStatus = NOERROR, got %+v", z)
|
||||||
}
|
|
||||||
if z.Status != "" {
|
|
||||||
t.Errorf("expected Status empty (no delegation block), got %q", z.Status)
|
|
||||||
}
|
|
||||||
if got := effectiveStatus(z); got != "NOERROR" {
|
|
||||||
t.Errorf("effectiveStatus: expected NOERROR fallback, got %q", got)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,10 @@ package checker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoCollector is returned by Collect when the provider was constructed
|
|
||||||
// without a CollectFn (e.g. when registered host-side as an externalizable-only
|
|
||||||
// provider). Callers should route the observation to an external checker.
|
|
||||||
var ErrNoCollector = errors.New("dnsviz: provider has no local collector; use an external checker")
|
|
||||||
|
|
||||||
// CollectFn is the function signature for the DNSViz data collection step.
|
// CollectFn is the function signature for the DNSViz data collection step.
|
||||||
// The checker package is decoupled from the subprocess invocation so it can
|
// The checker package is decoupled from the subprocess invocation so it can
|
||||||
// be imported without GPL obligations. Implementations live in the binary or
|
// be imported without GPL obligations. Implementations live in the binary or
|
||||||
|
|
@ -33,8 +27,5 @@ func (p *dnsvizProvider) Key() sdk.ObservationKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dnsvizProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) {
|
func (p *dnsvizProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) {
|
||||||
if p.collect == nil {
|
|
||||||
return nil, ErrNoCollector
|
|
||||||
}
|
|
||||||
return p.collect(ctx, opts)
|
return p.collect(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,15 +147,14 @@ func buildBanner(data *DNSVizData, states []sdk.CheckState) *bannerView {
|
||||||
z = data.Zones[leaf]
|
z = data.Zones[leaf]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eff := effectiveStatus(z)
|
st := statusFromGrok(z.Status)
|
||||||
st := statusFromGrok(eff)
|
|
||||||
if w := worstStatus(states); w > st {
|
if w := worstStatus(states); w > st {
|
||||||
st = w
|
st = w
|
||||||
}
|
}
|
||||||
return &bannerView{
|
return &bannerView{
|
||||||
Status: st.String(),
|
Status: st.String(),
|
||||||
Leaf: strings.TrimSuffix(leaf, "."),
|
Leaf: strings.TrimSuffix(leaf, "."),
|
||||||
LeafSt: emptyAsUnknown(eff),
|
LeafSt: emptyAsUnknown(z.Status),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,7 +381,7 @@ func renderChain(data *DNSVizData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeZoneBlock(b *strings.Builder, name string, idx, total int, z ZoneAnalysis, raw map[string]any) {
|
func writeZoneBlock(b *strings.Builder, name string, idx, total int, z ZoneAnalysis, raw map[string]any) {
|
||||||
st := statusFromGrok(effectiveStatus(z))
|
st := statusFromGrok(z.Status)
|
||||||
level := zoneLevelLabel(idx, total)
|
level := zoneLevelLabel(idx, total)
|
||||||
|
|
||||||
// Default-open zones with problems so the user sees them without
|
// Default-open zones with problems so the user sees them without
|
||||||
|
|
@ -400,9 +399,8 @@ func writeZoneBlock(b *strings.Builder, name string, idx, total int, z ZoneAnaly
|
||||||
if level != "" {
|
if level != "" {
|
||||||
fmt.Fprintf(b, `<span class="level">%s</span>`, html.EscapeString(level))
|
fmt.Fprintf(b, `<span class="level">%s</span>`, html.EscapeString(level))
|
||||||
}
|
}
|
||||||
eff := effectiveStatus(z)
|
fmt.Fprintf(b, `<span class="badge s-%s">%s</span>`, st.String(), html.EscapeString(emptyAsUnknown(z.Status)))
|
||||||
fmt.Fprintf(b, `<span class="badge s-%s">%s</span>`, st.String(), html.EscapeString(emptyAsUnknown(eff)))
|
if z.DNSStatus != "" && !strings.EqualFold(z.DNSStatus, z.Status) {
|
||||||
if z.DNSStatus != "" && !strings.EqualFold(z.DNSStatus, eff) {
|
|
||||||
fmt.Fprintf(b, `<span class="badge ghost">DNS: %s</span>`, html.EscapeString(z.DNSStatus))
|
fmt.Fprintf(b, `<span class="badge ghost">DNS: %s</span>`, html.EscapeString(z.DNSStatus))
|
||||||
}
|
}
|
||||||
if n := len(z.Errors); n > 0 {
|
if n := len(z.Errors); n > 0 {
|
||||||
|
|
|
||||||
|
|
@ -49,75 +49,6 @@ func orderedZones(data *DNSVizData) []string {
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// effectiveStatus returns the DNSSEC status for a zone, applying the
|
|
||||||
// inference chain that the observation layer deliberately leaves to rules:
|
|
||||||
// 1. delegation.status (z.Status) — the authoritative answer when present.
|
|
||||||
// 2. RRSIG-based inference — for zones without a delegation block (root),
|
|
||||||
// dnsviz surfaces trust-anchor validation only through per-RRSIG statuses.
|
|
||||||
// 3. DNS rcode fallback (z.DNSStatus) — when the zone is unsigned or grok
|
|
||||||
// did not classify it at all.
|
|
||||||
func effectiveStatus(z ZoneAnalysis) string {
|
|
||||||
if z.Status != "" {
|
|
||||||
return z.Status
|
|
||||||
}
|
|
||||||
if s := inferApexDNSKEYStatus(z.Queries); s != "" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return z.DNSStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// inferApexDNSKEYStatus returns "SECURE", "BOGUS", or "" based on the
|
|
||||||
// status of RRSIGs covering the zone's apex DNSKEY rrset. dnsviz attaches
|
|
||||||
// a per-RRSIG status whenever a key reaches it (either through DS from
|
|
||||||
// the parent or through a configured trust anchor at this zone). For
|
|
||||||
// the root, this is the only place where trust-anchor validation
|
|
||||||
// surfaces in the grok output.
|
|
||||||
//
|
|
||||||
// queries is the value at zone["queries"], a map keyed by
|
|
||||||
// "<zone>/IN/<RRTYPE>". We pick the DNSKEY query and look at every
|
|
||||||
// RRSIG inside its answer.
|
|
||||||
func inferApexDNSKEYStatus(queries map[string]any) string {
|
|
||||||
var dnskeyQ map[string]any
|
|
||||||
for k, v := range queries {
|
|
||||||
if !strings.HasSuffix(k, "/IN/DNSKEY") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if m, ok := v.(map[string]any); ok {
|
|
||||||
dnskeyQ = m
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dnskeyQ == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
answers, _ := dnskeyQ["answer"].([]any)
|
|
||||||
sawValid := false
|
|
||||||
for _, a := range answers {
|
|
||||||
am, _ := a.(map[string]any)
|
|
||||||
if am == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rrsigs, _ := am["rrsig"].([]any)
|
|
||||||
for _, rs := range rrsigs {
|
|
||||||
rm, _ := rs.(map[string]any)
|
|
||||||
if rm == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, _ := rm["status"].(string)
|
|
||||||
switch strings.ToUpper(s) {
|
|
||||||
case "INVALID", "BOGUS", "EXPIRED", "PREMATURE":
|
|
||||||
return "BOGUS"
|
|
||||||
case "VALID", "SECURE":
|
|
||||||
sawValid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sawValid {
|
|
||||||
return "SECURE"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func statusFromGrok(s string) sdk.Status {
|
func statusFromGrok(s string) sdk.Status {
|
||||||
switch strings.ToUpper(strings.TrimSpace(s)) {
|
switch strings.ToUpper(strings.TrimSpace(s)) {
|
||||||
case "SECURE":
|
case "SECURE":
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,13 @@ func (r *overallStatusRule) Evaluate(ctx context.Context, obs sdk.ObservationGet
|
||||||
leaf = zones[0]
|
leaf = zones[0]
|
||||||
z = data.Zones[leaf]
|
z = data.Zones[leaf]
|
||||||
}
|
}
|
||||||
eff := effectiveStatus(z)
|
|
||||||
st := sdk.CheckState{
|
st := sdk.CheckState{
|
||||||
Code: "dnsviz_overall_status",
|
Code: "dnsviz_overall_status",
|
||||||
Subject: leaf,
|
Subject: leaf,
|
||||||
Status: statusFromGrok(eff),
|
Status: statusFromGrok(z.Status),
|
||||||
Message: fmt.Sprintf("DNSViz status: %s", emptyAsUnknown(eff)),
|
Message: fmt.Sprintf("DNSViz status: %s", emptyAsUnknown(z.Status)),
|
||||||
Meta: map[string]any{
|
Meta: map[string]any{
|
||||||
"status": eff,
|
"status": z.Status,
|
||||||
"errors": len(z.Errors),
|
"errors": len(z.Errors),
|
||||||
"warnings": len(z.Warnings),
|
"warnings": len(z.Warnings),
|
||||||
},
|
},
|
||||||
|
|
@ -73,12 +72,11 @@ func (r *perZoneStatusRule) Evaluate(ctx context.Context, obs sdk.ObservationGet
|
||||||
out := make([]sdk.CheckState, 0, len(zones))
|
out := make([]sdk.CheckState, 0, len(zones))
|
||||||
for _, name := range zones {
|
for _, name := range zones {
|
||||||
z := data.Zones[name]
|
z := data.Zones[name]
|
||||||
eff := effectiveStatus(z)
|
|
||||||
out = append(out, sdk.CheckState{
|
out = append(out, sdk.CheckState{
|
||||||
Code: "dnsviz_per_zone_status",
|
Code: "dnsviz_per_zone_status",
|
||||||
Subject: name,
|
Subject: name,
|
||||||
Status: statusFromGrok(eff),
|
Status: statusFromGrok(z.Status),
|
||||||
Message: fmt.Sprintf("%s: errors=%d warnings=%d", emptyAsUnknown(eff), len(z.Errors), len(z.Warnings)),
|
Message: fmt.Sprintf("%s: errors=%d warnings=%d", emptyAsUnknown(z.Status), len(z.Errors), len(z.Warnings)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,8 @@ type DNSVizData struct {
|
||||||
// where the problem was found (DS, DNSKEY, RRSIG, NSEC proof, query
|
// where the problem was found (DS, DNSKEY, RRSIG, NSEC proof, query
|
||||||
// response, server, …). We walk the whole zone subtree to collect them.
|
// response, server, …). We walk the whole zone subtree to collect them.
|
||||||
type ZoneAnalysis struct {
|
type ZoneAnalysis struct {
|
||||||
// Status is the DNSSEC chain status reported directly under
|
// Status is the DNSSEC chain status taken from delegation.status when
|
||||||
// delegation.status in the grok output. Empty when no delegation block
|
// available, falling back to the top-level "status" field otherwise.
|
||||||
// is present (e.g. the root zone). Rules call effectiveStatus(z) rather
|
|
||||||
// than reading this field directly so that the RRSIG-based inference and
|
|
||||||
// the DNS-rcode fallback are applied consistently.
|
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
// DNSStatus is the raw top-level "status" field (DNS rcode such as
|
// DNSStatus is the raw top-level "status" field (DNS rcode such as
|
||||||
// "NOERROR"). Kept for the report so we can distinguish "DNS resolved
|
// "NOERROR"). Kept for the report so we can distinguish "DNS resolved
|
||||||
|
|
@ -68,11 +65,6 @@ type ZoneAnalysis struct {
|
||||||
DNSStatus string `json:"dns_status,omitempty"`
|
DNSStatus string `json:"dns_status,omitempty"`
|
||||||
Errors []Finding `json:"errors,omitempty"`
|
Errors []Finding `json:"errors,omitempty"`
|
||||||
Warnings []Finding `json:"warnings,omitempty"`
|
Warnings []Finding `json:"warnings,omitempty"`
|
||||||
// Queries holds the per-zone "queries" subtree from the grok output so
|
|
||||||
// that rules can infer the zone status from RRSIG validation when no
|
|
||||||
// delegation block is present (e.g. root zone, see inferApexDNSKEYStatus
|
|
||||||
// in rule.go).
|
|
||||||
Queries map[string]any `json:"queries,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finding mirrors the shape DNSViz uses for entries in errors/warnings.
|
// Finding mirrors the shape DNSViz uses for entries in errors/warnings.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue