110 lines
3 KiB
Go
110 lines
3 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// Rules returns the rule set surfaced to happyDomain. After the
|
|
// registry refactor we expose a single, generic rule that emits one
|
|
// CheckState per source result: the per-source verdict lives in
|
|
// CheckState.Subject (the source name) and CheckState.Code carries the
|
|
// canonical hit / clean / disabled / error flavour.
|
|
func Rules() []sdk.CheckRule {
|
|
return []sdk.CheckRule{&sourceRule{}}
|
|
}
|
|
|
|
type sourceRule struct{}
|
|
|
|
func (*sourceRule) Name() string { return "source_listed" }
|
|
func (*sourceRule) Description() string {
|
|
return "Emits one state per reputation source: Critical/Warning when the source flags the domain, OK when clean, Info when the source is disabled, and Warning on transient query errors."
|
|
}
|
|
|
|
func (*sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
var data BlacklistData
|
|
if err := obs.Get(ctx, ObservationKeyBlacklist, &data); err != nil {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusError,
|
|
Message: fmt.Sprintf("failed to get observation: %v", err),
|
|
Code: "blacklist_obs_error",
|
|
}}
|
|
}
|
|
|
|
if len(data.Results) == 0 {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusInfo, Message: "No reputation sources registered.",
|
|
Code: "blacklist_no_sources",
|
|
}}
|
|
}
|
|
|
|
out := make([]sdk.CheckState, 0, len(data.Results))
|
|
for _, r := range data.Results {
|
|
out = append(out, evaluateOne(r))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func evaluateOne(r SourceResult) sdk.CheckState {
|
|
subj := r.SourceName
|
|
if r.Subject != "" && r.Subject != r.SourceName {
|
|
subj = r.SourceName + " / " + r.Subject
|
|
}
|
|
switch {
|
|
case !r.Enabled:
|
|
return sdk.CheckState{
|
|
Status: sdk.StatusUnknown, Subject: subj,
|
|
Message: subj + ": disabled or not configured.",
|
|
Code: "source_disabled",
|
|
}
|
|
case r.BlockedQuery:
|
|
return sdk.CheckState{
|
|
Status: sdk.StatusError,
|
|
Subject: subj,
|
|
Message: fmt.Sprintf("%s: resolver is blocked, result unreliable: %s", subj, joinNonEmpty(r.Reasons, "; ")),
|
|
Code: "source_resolver_blocked",
|
|
}
|
|
case r.Error != "":
|
|
return sdk.CheckState{
|
|
Status: sdk.StatusWarn, Subject: subj,
|
|
Message: subj + ": query failed: " + r.Error,
|
|
Code: "source_error",
|
|
}
|
|
case r.Listed:
|
|
return sdk.CheckState{
|
|
Status: severityToStatus(r.Severity),
|
|
Subject: subj,
|
|
Message: fmt.Sprintf("Listed in %s: %s", subj, joinNonEmpty(r.Reasons, "; ")),
|
|
Code: "source_listed",
|
|
Meta: map[string]any{
|
|
"source_id": r.SourceID,
|
|
"reasons": r.Reasons,
|
|
"lookup_url": r.LookupURL,
|
|
"removal_url": r.RemovalURL,
|
|
"reference": r.Reference,
|
|
},
|
|
}
|
|
default:
|
|
return sdk.CheckState{
|
|
Status: sdk.StatusOK, Subject: subj,
|
|
Message: subj + ": clean.",
|
|
Code: "source_clean",
|
|
}
|
|
}
|
|
}
|
|
|
|
func severityToStatus(sev string) sdk.Status {
|
|
switch sev {
|
|
case SeverityCrit:
|
|
return sdk.StatusCrit
|
|
case SeverityWarn:
|
|
return sdk.StatusWarn
|
|
case SeverityInfo:
|
|
return sdk.StatusInfo
|
|
case SeverityOK:
|
|
return sdk.StatusOK
|
|
}
|
|
return sdk.StatusCrit
|
|
}
|