checker: rework target_resolvable to check existence (NOERROR) instead of A/AAAA

This commit is contained in:
nemunaire 2026-05-15 17:30:15 +08:00
commit 52a3e56c4f
3 changed files with 17 additions and 39 deletions

View file

@ -189,24 +189,10 @@ type targetResolvableRule struct{}
func (targetResolvableRule) Name() string { return "target_resolvable" } func (targetResolvableRule) Name() string { return "target_resolvable" }
func (targetResolvableRule) Description() string { func (targetResolvableRule) Description() string {
return "Verifies that the final target of the alias chain publishes at least one A or AAAA record." return "Verifies that the final target of the alias chain exists in DNS (returns NOERROR, not NXDOMAIN)."
} }
func (targetResolvableRule) Options() sdk.CheckerOptionsDocumentation { func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
return sdk.CheckerOptionsDocumentation{
UserOpts: []sdk.CheckerOptionDocumentation{
{
Id: "requireResolvableTarget",
Type: "bool",
Label: "Require resolvable target",
Description: "When enabled, a chain whose final target returns no A/AAAA is reported as critical (otherwise a warning).",
Default: defaultRequireResolvableTarget,
},
},
}
}
func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
data, errState := loadAlias(ctx, obs) data, errState := loadAlias(ctx, obs)
if errState != nil { if errState != nil {
return errState return errState
@ -217,22 +203,14 @@ func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGet
if data.ChainTerminated.Reason != TermOK { if data.ChainTerminated.Reason != TermOK {
return skipped("chain did not terminate normally") return skipped("chain did not terminate normally")
} }
if len(data.FinalA) > 0 || len(data.FinalAAAA) > 0 { if data.FinalRcode != "NXDOMAIN" {
return okState(data.FinalTarget, fmt.Sprintf("target %s resolves to %d address(es)", data.FinalTarget, len(data.FinalA)+len(data.FinalAAAA))) return okState(data.FinalTarget, fmt.Sprintf("target %s exists in DNS", data.FinalTarget))
}
status := sdk.StatusWarn
if sdk.GetBoolOption(opts, "requireResolvableTarget", defaultRequireResolvableTarget) {
status = sdk.StatusCrit
}
rcode := data.FinalRcode
if rcode == "" {
rcode = "no A/AAAA"
} }
return []sdk.CheckState{withHint(sdk.CheckState{ return []sdk.CheckState{withHint(sdk.CheckState{
Status: status, Status: sdk.StatusCrit,
Subject: data.FinalTarget, Subject: data.FinalTarget,
Message: fmt.Sprintf("final target %s does not resolve to an address (%s)", data.FinalTarget, rcode), Message: fmt.Sprintf("final target %s does not exist (NXDOMAIN)", data.FinalTarget),
}, "Point the alias at a name that publishes at least one A or AAAA record, or fix the upstream zone.")} }, "The alias points at a name that does not exist; create the missing record or update the alias target.")}
} }
type multipleRecordsRule struct{} type multipleRecordsRule struct{}

View file

@ -9,10 +9,9 @@ import (
// Defaults are centralised so Definition's docs and runtime reads cannot drift. // Defaults are centralised so Definition's docs and runtime reads cannot drift.
const ( const (
defaultMaxChainLength = 8 defaultMaxChainLength = 8
defaultMinTargetTTL = 60 defaultMinTargetTTL = 60
defaultRequireResolvableTarget = true defaultAllowApexCNAME = false
defaultAllowApexCNAME = false
defaultRecognizeApexFlattening = true defaultRecognizeApexFlattening = true
// hintKey is the CheckState.Meta key the HTML report reads to render the // hintKey is the CheckState.Meta key the HTML report reads to render the

View file

@ -290,24 +290,25 @@ func TestCnameDnssecRule(t *testing.T) {
} }
func TestTargetResolvableRule(t *testing.T) { func TestTargetResolvableRule(t *testing.T) {
t.Run("ok", func(t *testing.T) { t.Run("ok when NOERROR with A record", func(t *testing.T) {
d := apexKnownData() d := apexKnownData()
d.ChainTerminated.Reason = TermOK d.ChainTerminated.Reason = TermOK
d.FinalTarget = "target." d.FinalTarget = "target."
d.FinalA = []string{"1.2.3.4"} d.FinalA = []string{"1.2.3.4"}
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusOK) assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusOK)
}) })
t.Run("crit by default", func(t *testing.T) { t.Run("ok when NOERROR with no A/AAAA (e.g. service label)", func(t *testing.T) {
d := apexKnownData() d := apexKnownData()
d.ChainTerminated.Reason = TermOK d.ChainTerminated.Reason = TermOK
d.FinalTarget = "target." d.FinalTarget = "_2772._tcp.znc.example."
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusCrit) assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusOK)
}) })
t.Run("warn when requireResolvableTarget=false", func(t *testing.T) { t.Run("crit when NXDOMAIN", func(t *testing.T) {
d := apexKnownData() d := apexKnownData()
d.ChainTerminated.Reason = TermOK d.ChainTerminated.Reason = TermOK
d.FinalTarget = "target." d.FinalTarget = "target."
assertSingle(t, run(targetResolvableRule{}, d, sdk.CheckerOptions{"requireResolvableTarget": false}), sdk.StatusWarn) d.FinalRcode = "NXDOMAIN"
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusCrit)
}) })
t.Run("skip when chain did not terminate normally", func(t *testing.T) { t.Run("skip when chain did not terminate normally", func(t *testing.T) {
d := apexKnownData() d := apexKnownData()