checker-ns-restrictions/checker/rule.go

134 lines
3.5 KiB
Go

// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package checker
import (
"context"
"fmt"
"strings"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// failLevels associates each check name with the severity it raises when it
// fails. Names must match those produced by checkServerAddr.
var failLevels = map[string]int{
"AXFR refused": statusCrit,
"IXFR refused": statusWarn,
"No recursion": statusWarn,
"ANY handled (RFC 8482)": statusWarn,
"Is authoritative": statusInfo,
}
// Rule returns a new NS restrictions evaluation rule.
func Rule() sdk.CheckRule {
return &nsRule{}
}
type nsRule struct{}
func (r *nsRule) Name() string { return "ns_restrictions_check" }
func (r *nsRule) Description() string {
return "Checks nameservers for AXFR/IXFR acceptance, recursion availability, RFC 8482 ANY handling and authoritative status"
}
func (r *nsRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState {
var report NSRestrictionsReport
if err := obs.Get(ctx, ObservationKeyNSRestrictions, &report); err != nil {
return sdk.CheckState{
Status: sdk.StatusError,
Message: fmt.Sprintf("Failed to get NS restrictions data: %v", err),
Code: "ns_restrictions_error",
}
}
overall := statusOK
var summaryParts []string
for _, srv := range report.Servers {
serverWorst := statusOK
for _, item := range srv.Checks {
if item.OK {
continue
}
level, ok := failLevels[item.Name]
if !ok {
// Unknown check (e.g. "DNS resolution", "IPv6 connectivity")
// — treat a failure as a warning by default.
level = statusWarn
}
if level > serverWorst {
serverWorst = level
}
}
if serverWorst > overall {
overall = serverWorst
}
label := srv.Name
if srv.Address != "" {
label = fmt.Sprintf("%s (%s)", srv.Name, srv.Address)
}
summaryParts = append(summaryParts, fmt.Sprintf("%s: %s", label, statusName(serverWorst)))
}
return sdk.CheckState{
Status: toSDKStatus(overall),
Message: strings.Join(summaryParts, " | "),
Code: "ns_restrictions_result",
Meta: map[string]any{
"servers": report.Servers,
},
}
}
func statusName(s int) string {
switch s {
case statusOK:
return "OK"
case statusInfo:
return "INFO"
case statusWarn:
return "WARN"
case statusCrit:
return "CRITICAL"
default:
return "UNKNOWN"
}
}
func toSDKStatus(s int) sdk.Status {
switch s {
case statusOK:
return sdk.StatusOK
case statusInfo:
return sdk.StatusInfo
case statusWarn:
return sdk.StatusWarn
case statusCrit:
return sdk.StatusCrit
default:
return sdk.StatusUnknown
}
}