Initial commit
This commit is contained in:
commit
a6dbcef0f9
26 changed files with 2993 additions and 0 deletions
80
checker/rules_match.go
Normal file
80
checker/rules_match.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// recordsMatchChainRule is the core DANE check: for every endpoint whose
|
||||
// handshake succeeded, at least one declared TLSA record must match the
|
||||
// certificate chain presented by the server (RFC 6698 §2.1 OR semantics).
|
||||
//
|
||||
// This is the most common DANE outage vector, a certificate rotation
|
||||
// without a matching TLSA rollover, so it deserves its own rule and its
|
||||
// own per-endpoint states.
|
||||
type recordsMatchChainRule struct{}
|
||||
|
||||
func (r *recordsMatchChainRule) Name() string { return "dane.records_match_chain" }
|
||||
func (r *recordsMatchChainRule) Description() string {
|
||||
return "Verifies that at least one TLSA record matches the certificate chain presented by each endpoint."
|
||||
}
|
||||
|
||||
func (r *recordsMatchChainRule) 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 out []sdk.CheckState
|
||||
tested := 0
|
||||
for _, t := range rc.data.Targets {
|
||||
probe := rc.probes[t.Ref]
|
||||
if !probeUsable(probe) {
|
||||
continue // covered by probeAvailableRule / handshakeOKRule
|
||||
}
|
||||
if len(t.Records) == 0 {
|
||||
continue // covered by hasRecordsRule
|
||||
}
|
||||
tested++
|
||||
subj := targetSubject(t)
|
||||
meta := targetMeta(t)
|
||||
|
||||
s := summarizeMatches(t, probe)
|
||||
meta["matched"] = s.matched
|
||||
meta["unmatched"] = s.unmatched
|
||||
|
||||
if s.matched > 0 {
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusOK,
|
||||
Code: "dane_match_ok",
|
||||
Subject: subj,
|
||||
Message: fmt.Sprintf("%d/%d TLSA record(s) match the presented certificate chain.", s.matched, s.matched+s.unmatched),
|
||||
Meta: meta,
|
||||
})
|
||||
continue
|
||||
}
|
||||
msg := "No TLSA record matches the presented certificate chain."
|
||||
if s.firstUnmatchedReason != "" {
|
||||
msg += " " + s.firstUnmatchedReason
|
||||
}
|
||||
meta["first_unmatched_index"] = s.firstUnmatchedIdx
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusCrit,
|
||||
Code: "dane_no_match",
|
||||
Subject: subj,
|
||||
Message: msg,
|
||||
Meta: meta,
|
||||
})
|
||||
}
|
||||
if len(out) == 0 {
|
||||
if tested == 0 {
|
||||
return []sdk.CheckState{{
|
||||
Status: sdk.StatusUnknown,
|
||||
Code: "dane_records_match_chain_skipped",
|
||||
Message: "No usable probe/records pair to evaluate.",
|
||||
}}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue