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 }