checker: add domain length validation and refactor rules into per-concern checks
This commit is contained in:
parent
df0d429150
commit
946ec446d2
15 changed files with 716 additions and 308 deletions
|
|
@ -305,12 +305,19 @@ th { font-weight: 600; color: #6b7280; }
|
|||
// GetHTMLReport implements sdk.CheckerHTMLReporter. It folds in related TLS
|
||||
// observations so the XMPP service page shows cert posture directly, without
|
||||
// the user having to open a separate TLS report.
|
||||
//
|
||||
// The hint/fix section is driven exclusively by ctx.States(): it is the host
|
||||
// that has already evaluated every rule and handed us the resulting
|
||||
// CheckStates. The report never re-derives issues from the raw observation
|
||||
// so there is no duplicated judgment logic. When States() is empty (for
|
||||
// example a standalone render with no rule run), we still show the raw
|
||||
// facts (SRV table, endpoint details) but drop the actionable hints.
|
||||
func (p *xmppProvider) GetHTMLReport(rctx sdk.ReportContext) (string, error) {
|
||||
var d XMPPData
|
||||
if err := json.Unmarshal(rctx.Data(), &d); err != nil {
|
||||
return "", fmt.Errorf("unmarshal xmpp observation: %w", err)
|
||||
}
|
||||
view := buildReportData(&d, rctx.Related(TLSRelatedKey))
|
||||
view := buildReportData(&d, rctx.Related(TLSRelatedKey), rctx.States())
|
||||
return renderReport(view)
|
||||
}
|
||||
|
||||
|
|
@ -322,12 +329,15 @@ func renderReport(view reportData) (string, error) {
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func buildReportData(d *XMPPData, related []sdk.RelatedObservation) reportData {
|
||||
tlsIssues := tlsIssuesFromRelated(related)
|
||||
func buildReportData(d *XMPPData, related []sdk.RelatedObservation, states []sdk.CheckState) reportData {
|
||||
tlsByAddr := indexTLSByAddress(related)
|
||||
|
||||
allIssues := append([]Issue(nil), d.Issues...)
|
||||
allIssues = append(allIssues, tlsIssues...)
|
||||
// Fix list comes exclusively from the CheckStates the host evaluated.
|
||||
// When no states were supplied (standalone renders, one-off tests),
|
||||
// the hint section is skipped entirely: we show raw facts only,
|
||||
// never re-judge the observation here.
|
||||
fixes := fixesFromStates(states)
|
||||
hasStates := len(states) > 0
|
||||
|
||||
view := reportData{
|
||||
Domain: d.Domain,
|
||||
|
|
@ -338,35 +348,38 @@ func buildReportData(d *XMPPData, related []sdk.RelatedObservation) reportData {
|
|||
HasIPv6: d.Coverage.HasIPv6,
|
||||
WorkingC2S: d.Coverage.WorkingC2S,
|
||||
WorkingS2S: d.Coverage.WorkingS2S,
|
||||
HasIssues: len(allIssues) > 0,
|
||||
HasIssues: len(fixes) > 0,
|
||||
HasTLSPosture: len(tlsByAddr) > 0,
|
||||
}
|
||||
|
||||
// Status banner.
|
||||
worst := SeverityInfo
|
||||
for _, is := range allIssues {
|
||||
if is.Severity == SeverityCrit {
|
||||
worst = SeverityCrit
|
||||
break
|
||||
}
|
||||
if is.Severity == SeverityWarn {
|
||||
worst = SeverityWarn
|
||||
}
|
||||
}
|
||||
if len(allIssues) == 0 {
|
||||
view.StatusLabel = "OK"
|
||||
view.StatusClass = "ok"
|
||||
// Status banner: driven by the worst CheckState when available,
|
||||
// otherwise a neutral label (data-only render).
|
||||
if !hasStates {
|
||||
view.StatusLabel = "DATA"
|
||||
view.StatusClass = "muted"
|
||||
} else {
|
||||
worst := sdk.StatusOK
|
||||
for _, s := range states {
|
||||
if s.Status > worst {
|
||||
worst = s.Status
|
||||
}
|
||||
}
|
||||
switch worst {
|
||||
case SeverityCrit:
|
||||
case sdk.StatusCrit, sdk.StatusError:
|
||||
view.StatusLabel = "FAIL"
|
||||
view.StatusClass = "fail"
|
||||
case SeverityWarn:
|
||||
case sdk.StatusWarn:
|
||||
view.StatusLabel = "WARN"
|
||||
view.StatusClass = "warn"
|
||||
default:
|
||||
case sdk.StatusInfo:
|
||||
view.StatusLabel = "INFO"
|
||||
view.StatusClass = "muted"
|
||||
case sdk.StatusUnknown:
|
||||
view.StatusLabel = "UNKNOWN"
|
||||
view.StatusClass = "muted"
|
||||
default:
|
||||
view.StatusLabel = "OK"
|
||||
view.StatusClass = "ok"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,16 +394,8 @@ func buildReportData(d *XMPPData, related []sdk.RelatedObservation) reportData {
|
|||
return 2
|
||||
}
|
||||
}
|
||||
sort.SliceStable(allIssues, func(i, j int) bool { return sevRank(allIssues[i].Severity) < sevRank(allIssues[j].Severity) })
|
||||
for _, is := range allIssues {
|
||||
view.Fixes = append(view.Fixes, reportFix{
|
||||
Severity: is.Severity,
|
||||
Code: is.Code,
|
||||
Message: is.Message,
|
||||
Fix: is.Fix,
|
||||
Endpoint: is.Endpoint,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(fixes, func(i, j int) bool { return sevRank(fixes[i].Severity) < sevRank(fixes[j].Severity) })
|
||||
view.Fixes = fixes
|
||||
|
||||
// SRV rows.
|
||||
addSRV := func(prefix string, records []SRVRecord) {
|
||||
|
|
@ -462,6 +467,42 @@ func buildReportData(d *XMPPData, related []sdk.RelatedObservation) reportData {
|
|||
return view
|
||||
}
|
||||
|
||||
// fixesFromStates turns CheckStates handed to us by the host into the
|
||||
// severity-tagged entries rendered in the "What to fix" section. It is
|
||||
// intentionally the only source of hints on the report: the raw
|
||||
// observation is never re-judged here.
|
||||
func fixesFromStates(states []sdk.CheckState) []reportFix {
|
||||
var out []reportFix
|
||||
for _, s := range states {
|
||||
var sev string
|
||||
switch s.Status {
|
||||
case sdk.StatusCrit, sdk.StatusError:
|
||||
sev = SeverityCrit
|
||||
case sdk.StatusWarn:
|
||||
sev = SeverityWarn
|
||||
case sdk.StatusInfo:
|
||||
sev = SeverityInfo
|
||||
default:
|
||||
// OK / Unknown: not an actionable finding.
|
||||
continue
|
||||
}
|
||||
fix := ""
|
||||
if s.Meta != nil {
|
||||
if v, ok := s.Meta["fix"].(string); ok {
|
||||
fix = v
|
||||
}
|
||||
}
|
||||
out = append(out, reportFix{
|
||||
Severity: sev,
|
||||
Code: s.Code,
|
||||
Message: s.Message,
|
||||
Fix: fix,
|
||||
Endpoint: s.Subject,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func modeLabel(m XMPPMode) string {
|
||||
switch m {
|
||||
case ModeClient:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue