checker-email-keys/checker/rule.go

114 lines
3 KiB
Go

package checker
import (
"context"
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// Rule returns the single aggregation rule for this checker. It folds
// every finding produced by Collect into a CheckState whose status is
// the worst severity seen.
func Rule() sdk.CheckRule {
return &emailKeyRule{}
}
type emailKeyRule struct{}
func (r *emailKeyRule) Name() string { return "openpgpkey_smimea_check" }
func (r *emailKeyRule) Description() string {
return "Validates a DNS-published OpenPGP key (RFC 7929) or S/MIME certificate (RFC 8162), running DNSSEC, record-hash, parse, expiration, algorithm-strength, and S/MIME EKU checks."
}
func (r *emailKeyRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
var data EmailKeyData
if err := obs.Get(ctx, ObservationKey, &data); err != nil {
return []sdk.CheckState{{
Status: sdk.StatusError,
Message: fmt.Sprintf("Failed to read observation %q: %v", ObservationKey, err),
Code: "openpgpkey_observation_error",
}}
}
return []sdk.CheckState{evaluate(&data)}
}
// evaluate folds findings into a CheckState. The status is the highest
// severity observed: any Crit makes the whole result Crit, any Warn
// makes it Warn, otherwise Info/OK.
func evaluate(data *EmailKeyData) sdk.CheckState {
var crit, warn, info int
var firstCrit, firstWarn, firstInfo string
for _, f := range data.Findings {
switch f.Severity {
case SeverityCrit:
crit++
if firstCrit == "" {
firstCrit = f.Message
}
case SeverityWarn:
warn++
if firstWarn == "" {
firstWarn = f.Message
}
case SeverityInfo:
info++
if firstInfo == "" {
firstInfo = f.Message
}
}
}
status := sdk.StatusOK
msg := summariseHealthy(data)
code := "openpgpkey_ok"
switch {
case crit > 0:
status = sdk.StatusCrit
msg = firstCrit
code = "openpgpkey_crit"
case warn > 0:
status = sdk.StatusWarn
msg = firstWarn
code = "openpgpkey_warn"
case info > 0:
status = sdk.StatusInfo
msg = firstInfo
code = "openpgpkey_info"
}
meta := map[string]any{
"kind": data.Kind,
"queried": data.QueriedOwner,
"record_count": data.RecordCount,
"findings": data.Findings,
}
if data.DNSSECSecure != nil {
meta["dnssec_secure"] = *data.DNSSECSecure
}
return sdk.CheckState{
Status: status,
Message: msg,
Code: code,
Subject: data.QueriedOwner,
Meta: meta,
}
}
func summariseHealthy(data *EmailKeyData) string {
switch data.Kind {
case KindOpenPGPKey:
if data.OpenPGP != nil && data.OpenPGP.Fingerprint != "" {
return fmt.Sprintf("OPENPGPKEY %s published and valid (fingerprint %s)", data.QueriedOwner, data.OpenPGP.Fingerprint)
}
return fmt.Sprintf("OPENPGPKEY %s published and valid", data.QueriedOwner)
case KindSMIMEA:
if data.SMIMEA != nil && data.SMIMEA.Certificate != nil {
return fmt.Sprintf("SMIMEA %s valid (subject %s)", data.QueriedOwner, data.SMIMEA.Certificate.Subject)
}
return fmt.Sprintf("SMIMEA %s published and valid", data.QueriedOwner)
}
return "Record validated"
}