diff --git a/checker/interactive.go b/checker/interactive.go index 3d92759..18ff51d 100644 --- a/checker/interactive.go +++ b/checker/interactive.go @@ -36,7 +36,6 @@ func (p *caaProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) { return nil, errors.New("domain is required") } domain = dns.Fqdn(domain) - bare := strings.TrimSuffix(domain, ".") records, err := lookupCAA(domain) if err != nil { @@ -56,12 +55,12 @@ func (p *caaProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) { svc := serviceMessage{ Type: serviceType, - Domain: bare, + Domain: strings.TrimSuffix(domain, "."), Service: svcBody, } return sdk.CheckerOptions{ - "domain": bare, + "domain": strings.TrimSuffix(domain, "."), "service": svc, }, nil } @@ -75,12 +74,12 @@ func lookupCAA(fqdn string) ([]CAARecord, error) { return nil, err } - c := new(dns.Client) for name := fqdn; name != "" && name != "."; { msg := new(dns.Msg) msg.SetQuestion(name, dns.TypeCAA) msg.RecursionDesired = true + c := new(dns.Client) in, _, err := c.Exchange(msg, resolver) if err != nil { return nil, err diff --git a/checker/rule.go b/checker/rule.go index 20c166b..512795f 100644 --- a/checker/rule.go +++ b/checker/rule.go @@ -32,6 +32,7 @@ type issuerAgg struct { severity string code string msg string + fix string endpoints map[string]bool } @@ -116,7 +117,7 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts // info just because one probe happened to be unresolvable. agg := map[string]*issuerAgg{} // keyed by AKI+DN - issue := func(p *tlsProbeView, severity, code, msg string) { + issue := func(p *tlsProbeView, severity, code, msg, fix string) { k := p.IssuerAKI + "|" + p.IssuerDN cur, ok := agg[k] if !ok { @@ -127,6 +128,7 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts cur.severity = severity cur.code = code cur.msg = msg + cur.fix = fix } if addr := p.address(); addr != "" { cur.endpoints[addr] = true @@ -136,14 +138,16 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts for _, p := range probes { if al.disallowIssue { issue(p, SeverityCrit, CodeIssuanceDisallowed, - fmt.Sprintf("CAA policy forbids issuance (issue \";\") but a certificate was observed on %s", p.address())) + fmt.Sprintf("CAA policy forbids issuance (issue \";\") but a certificate was observed on %s", p.address()), + "Remove the CA 0 issue \";\" record, or stop serving TLS certificates for this domain.") continue } domains, ok := Lookup(p.IssuerAKI, p.IssuerDN) if !ok { issue(p, SeverityInfo, CodeIssuerUnknown, - fmt.Sprintf("Observed issuer not found in CCADB (AKI=%q, DN=%q)", p.IssuerAKI, p.IssuerDN)) + fmt.Sprintf("Observed issuer not found in CCADB (AKI=%q, DN=%q)", p.IssuerAKI, p.IssuerDN), + "File a CCADB update for this CA, or verify the cert was issued by a real CA.") continue } @@ -154,18 +158,21 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts if !hasPolicy { issue(p, SeverityInfo, CodeOK, fmt.Sprintf("No CAA records published; certificate on %s issued by %s (CAA identifier %s).", - p.address(), issuerLabel(p), strings.Join(domains, ", "))) + p.address(), issuerLabel(p), strings.Join(domains, ", ")), + fmt.Sprintf("Publish %q IN CAA 0 issue %q to lock future issuance to %s.", + data.Domain, domains[0], domains[0])) continue } if !intersects(domains, al.issueAll) { issue(p, SeverityCrit, CodeNotAuthorized, fmt.Sprintf("Certificate on %s issued by %s (CAA identifier %s) is not authorized by the zone's CAA issue records", - p.address(), issuerLabel(p), strings.Join(domains, ", "))) + p.address(), issuerLabel(p), strings.Join(domains, ", ")), + "Either update the CAA record to authorize this CA, or re-issue with an authorized CA.") continue } - issue(p, "", "", "") + issue(p, "", "", "", "") } // Emit one CheckState per distinct issuer, keyed deterministically so @@ -232,19 +239,6 @@ func severityRank(s string) int { } } -func severityToStatus(s string) sdk.Status { - switch s { - case SeverityCrit: - return sdk.StatusCrit - case SeverityWarn: - return sdk.StatusWarn - case SeverityInfo: - return sdk.StatusInfo - default: - return sdk.StatusOK - } -} - // intersects reports whether any element of lhs is present in the set. func intersects(lhs []string, set map[string]bool) bool { for _, s := range lhs {