checker-dane/checker/rules_records.go

63 lines
2 KiB
Go

package checker
import (
"context"
"fmt"
"strings"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// hasRecordsRule reports whether the TLSAs service declares any TLSA record
// at all. Without records there is nothing for DANE to validate.
type hasRecordsRule struct{}
func (r *hasRecordsRule) Name() string { return "dane.has_records" }
func (r *hasRecordsRule) Description() string {
return "Verifies that at least one TLSA record is declared on the service."
}
func (r *hasRecordsRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
rc := loadRuleContext(ctx, obs)
if rc.err != nil {
return []sdk.CheckState{observationErrorState(rc.err)}
}
var states []sdk.CheckState
for _, inv := range rc.data.Invalid {
states = append(states, sdk.CheckState{
Status: sdk.StatusError,
Code: "dane_invalid_owner",
Subject: inv.Owner,
Message: fmt.Sprintf("TLSA record %q is unusable: %s", inv.Owner, inv.Reason),
Meta: map[string]any{"owner": inv.Owner, "reason": inv.Reason},
})
}
if len(rc.data.Targets) == 0 {
if len(states) > 0 {
// Records exist but none are usable; flag the aggregate too so
// the UI doesn't only show per-record errors.
owners := make([]string, 0, len(rc.data.Invalid))
for _, inv := range rc.data.Invalid {
owners = append(owners, inv.Owner)
}
states = append(states, sdk.CheckState{
Status: sdk.StatusError,
Code: "dane_no_usable_records",
Message: fmt.Sprintf("No usable TLSA records (all %d declared records are malformed: %s).", len(rc.data.Invalid), strings.Join(owners, ", ")),
})
return states
}
return []sdk.CheckState{{
Status: sdk.StatusUnknown,
Code: "dane_no_records",
Message: "No TLSA records declared on this service.",
}}
}
states = append(states, sdk.CheckState{
Status: sdk.StatusOK,
Code: "dane_has_records_ok",
Message: "TLSA records are declared for all bound endpoints.",
Meta: map[string]any{"endpoints": len(rc.data.Targets)},
})
return states
}