Use ctx.States() for remediation cards in HTML report
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Gate each remediation on the presence of the corresponding rule state code rather than re-deriving from raw observation fields; falls back to raw-data analysis when states are absent.
This commit is contained in:
parent
b3a13168de
commit
de87a8fef1
1 changed files with 38 additions and 50 deletions
|
|
@ -180,7 +180,7 @@ func (p *kerberosProvider) GetHTMLReport(rctx sdk.ReportContext) (string, error)
|
|||
// Detect common failures and build the remediation banner. Hints are
|
||||
// only surfaced when the host supplied rule states for this run.
|
||||
if hasStates {
|
||||
rd.Remediations = buildRemediations(&r, rd)
|
||||
rd.Remediations = buildRemediations(&r, rd, states)
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
|
|
@ -190,20 +190,21 @@ func (p *kerberosProvider) GetHTMLReport(rctx sdk.ReportContext) (string, error)
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// buildRemediations inspects the observation and returns actionable hints
|
||||
// for the user-visible failures. Only matching hints are appended, so a
|
||||
// healthy realm shows an empty list (rendered as nothing).
|
||||
func buildRemediations(r *KerberosData, rd reportData) []remediation {
|
||||
var out []remediation
|
||||
|
||||
hasKDCSRV := false
|
||||
for _, b := range r.SRV {
|
||||
if strings.HasPrefix(b.Prefix, "_kerberos.") && len(b.Records) > 0 {
|
||||
hasKDCSRV = true
|
||||
break
|
||||
// buildRemediations returns actionable hints keyed by rule state codes so the
|
||||
// report and the rule output never disagree on which conditions are present.
|
||||
// Display-only fields (realm name, clock skew string, unresolved host list)
|
||||
// are still read from raw data — they are presentation, not verdict.
|
||||
func buildRemediations(r *KerberosData, rd reportData, states []sdk.CheckState) []remediation {
|
||||
byCode := make(map[string]sdk.CheckState, len(states))
|
||||
for _, st := range states {
|
||||
if st.Status == sdk.StatusCrit || st.Status == sdk.StatusWarn || st.Status == sdk.StatusError {
|
||||
byCode[st.Code] = st
|
||||
}
|
||||
}
|
||||
if !hasKDCSRV {
|
||||
|
||||
var out []remediation
|
||||
|
||||
if _, ok := byCode[CodeNoSRV]; ok {
|
||||
out = append(out, remediation{
|
||||
Title: "Publish Kerberos SRV records",
|
||||
Body: template.HTML(fmt.Sprintf(
|
||||
|
|
@ -213,41 +214,31 @@ _kerberos._udp.%[1]s. IN SRV 0 0 88 kdc.%[1]s.</pre>`, strings.ToLower(r.Realm)
|
|||
})
|
||||
}
|
||||
|
||||
// SRV targets that don't resolve.
|
||||
var unresolved []string
|
||||
for _, h := range rd.Resolution {
|
||||
if h.Error != "" || (len(h.IPv4) == 0 && len(h.IPv6) == 0) {
|
||||
unresolved = append(unresolved, h.Target)
|
||||
}
|
||||
}
|
||||
if len(unresolved) > 0 {
|
||||
out = append(out, remediation{
|
||||
Title: "Resolve KDC host names",
|
||||
Body: template.HTML(fmt.Sprintf(
|
||||
`The following SRV target(s) do not resolve to an IP address: <code>%s</code>. Add A/AAAA records for each host, or correct the SRV target.`,
|
||||
template.HTMLEscapeString(strings.Join(unresolved, ", ")))),
|
||||
})
|
||||
}
|
||||
|
||||
// No KDC reachable (port filtered / host down).
|
||||
reachable := 0
|
||||
totalKDC := 0
|
||||
for _, p := range r.Probes {
|
||||
if p.Role == "kdc" {
|
||||
totalKDC++
|
||||
if p.OK {
|
||||
reachable++
|
||||
if _, ok := byCode[CodeKDCUnreachable]; ok {
|
||||
// Use raw resolution data to show which hosts are unresolved (display only).
|
||||
var unresolved []string
|
||||
for _, h := range rd.Resolution {
|
||||
if h.Error != "" || (len(h.IPv4) == 0 && len(h.IPv6) == 0) {
|
||||
unresolved = append(unresolved, h.Target)
|
||||
}
|
||||
}
|
||||
}
|
||||
if totalKDC > 0 && reachable == 0 {
|
||||
out = append(out, remediation{
|
||||
Title: "Open port 88 on KDC hosts",
|
||||
Body: template.HTML(`Every KDC endpoint refused or timed out. Ensure your firewall allows inbound <code>TCP 88</code> and <code>UDP 88</code>, and that the KDC process is listening on the SRV target's IP.`),
|
||||
})
|
||||
if len(unresolved) > 0 {
|
||||
out = append(out, remediation{
|
||||
Title: "Resolve KDC host names",
|
||||
Body: template.HTML(fmt.Sprintf(
|
||||
`The following SRV target(s) do not resolve to an IP address: <code>%s</code>. Add A/AAAA records for each host, or correct the SRV target.`,
|
||||
template.HTMLEscapeString(strings.Join(unresolved, ", ")))),
|
||||
})
|
||||
} else {
|
||||
out = append(out, remediation{
|
||||
Title: "Open port 88 on KDC hosts",
|
||||
Body: template.HTML(`Every KDC endpoint refused or timed out. Ensure your firewall allows inbound <code>TCP 88</code> and <code>UDP 88</code>, and that the KDC process is listening on the SRV target's IP.`),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Clock skew.
|
||||
// Clock skew is already driven by CodeClockSkewBad in the caller; pass
|
||||
// the display-enriched flag through rather than re-checking states here.
|
||||
if rd.ClockSkewBad {
|
||||
out = append(out, remediation{
|
||||
Title: "Synchronize clocks",
|
||||
|
|
@ -257,8 +248,7 @@ _kerberos._udp.%[1]s. IN SRV 0 0 88 kdc.%[1]s.</pre>`, strings.ToLower(r.Realm)
|
|||
})
|
||||
}
|
||||
|
||||
// Weak crypto only.
|
||||
if rd.HasWeakOnly {
|
||||
if _, ok := byCode[CodeEnctypesWeakOnly]; ok {
|
||||
out = append(out, remediation{
|
||||
Title: "Retire DES/RC4 enctypes",
|
||||
Body: template.HTML(`The KDC only advertises weak encryption types. Configure at least AES:
|
||||
|
|
@ -270,8 +260,7 @@ then rekey principals with <code>kadmin -q "cpw -randkey principal"</code> or eq
|
|||
})
|
||||
}
|
||||
|
||||
// Wrong realm in KDC reply.
|
||||
if r.AS.ServerRealm != "" && !strings.EqualFold(r.AS.ServerRealm, r.Realm) {
|
||||
if _, ok := byCode[CodeASWrongRealm]; ok {
|
||||
out = append(out, remediation{
|
||||
Title: "Fix realm mismatch",
|
||||
Body: template.HTML(fmt.Sprintf(
|
||||
|
|
@ -281,8 +270,7 @@ then rekey principals with <code>kadmin -q "cpw -randkey principal"</code> or eq
|
|||
})
|
||||
}
|
||||
|
||||
// AS-REP without preauth, AS-REP roasting.
|
||||
if r.AS.Attempted && r.AS.PrincipalFound && !r.AS.PreauthReq {
|
||||
if _, ok := byCode[CodeASRepNoPreauth]; ok {
|
||||
out = append(out, remediation{
|
||||
Title: "Enable pre-authentication",
|
||||
Body: template.HTML(`The KDC returned an AS-REP without demanding pre-authentication. This exposes principals to <strong>AS-REP roasting</strong>. Enable <code>requires_preauth</code> (MIT) or the equivalent flag on every user principal, e.g. <code>kadmin -q "modprinc +requires_preauth user@REALM"</code>.`),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue