No description
  • Go 97.5%
  • Makefile 1.5%
  • Dockerfile 1%
Find a file
2026-04-23 09:12:00 +07:00
checker Initial commit 2026-04-23 09:12:00 +07:00
plugin Initial commit 2026-04-23 09:12:00 +07:00
.gitignore Initial commit 2026-04-23 09:12:00 +07:00
Dockerfile Initial commit 2026-04-23 09:12:00 +07:00
go.mod Initial commit 2026-04-23 09:12:00 +07:00
go.sum Initial commit 2026-04-23 09:12:00 +07:00
LICENSE Initial commit 2026-04-23 09:12:00 +07:00
main.go Initial commit 2026-04-23 09:12:00 +07:00
Makefile Initial commit 2026-04-23 09:12:00 +07:00
NOTICE Initial commit 2026-04-23 09:12:00 +07:00
README.md Initial commit 2026-04-23 09:12:00 +07:00

checker-caa

CAA posture checker for happyDomain.

Validates that certificates observed by checker-tls were issued by a CA actually authorized by the domain's CAA records. This checker runs no network probes of its own: it reads the svcs.CAAPolicy service body (already parsed by happyDomain from the zone's CAA resource records) and the tls_probes observations published by checker-tls, and cross-references them via the CCADB "CAA Identifiers" mapping.

How it works

  1. The host runs this checker on a svcs.CAAPolicy service.
  2. Collect unmarshals the service body into a list of (flag, tag, value) entries. No network.
  3. The caa_compliance rule:
    • calls obs.Get("caa_policy", …) to load its own payload;
    • calls obs.GetRelated("tls_probes") to pick up every TLS probe produced on the target;
    • resolves each observed issuer (keyed by IssuerAKI with an IssuerDN fallback) against the embedded CCADB CSV to find the CA's published CAA identifier domain(s);
    • compares the observed identifiers against the issue / issuewild allow list (or flags a DisallowIssue violation).

Observation payload

This checker does not publish endpoints or add a new observation schema. Under its own observation key caa_policy it returns a pass-through view of the zone-side CAA records:

{
  "domain": "example.net",
  "records": [
    { "flag": 0, "tag": "issue",     "value": "letsencrypt.org" },
    { "flag": 0, "tag": "issuewild", "value": ";" }
  ],
  "run_at": "2026-04-22T12:34:56Z"
}

Rule outcomes

  • caa_ok: every observed issuer is authorized by the zone's CAA policy.
  • caa_no_tls: no TLS probes related to this target have been published yet. Reported as UNKNOWN (the same "eventual consistency" steady state used by checker-tls when it has no endpoints yet).
  • caa_not_authorized: CCADB mapped an observed issuer to a domain the CAA policy does not list. Reported CRIT.
  • caa_issuance_disallowed: the policy contains CAA 0 issue ";" (explicitly disallowing issuance) but a TLS cert was still observed. Reported CRIT.
  • caa_issuer_unknown: CCADB has no mapping for the observed issuer (AKI + DN). Reported INFO; action is to file a CCADB update.

Issuer → CAA domain mapping (CCADB)

The file checker/AllCAAIdentifiersReport.csv is an unmodified snapshot of the "CAA Identifiers (V2)" report from the Common CA Database (https://www.ccadb.org/resources). It is embedded into the binary via //go:embed. To refresh it, download the CSV from CCADB and replace the file; no code changes needed. A future make update-ccadb target will automate this.

The lookup key is:

  1. IssuerAKI (uppercase hex of leaf.AuthorityKeyId), matched against CCADB's "Subject Key Identifier (Hex)" column.
  2. IssuerDN (Go's leaf.Issuer.String()), matched against CCADB's "Subject" column after normalization (RDNs sorted by type, whitespace trimmed, comma/semicolon separators collapsed).

Options

Id Type Default Description
domain string (auto) Domain being checked (AutoFill).
service (n/a) (auto) svcs.CAAPolicy service body.

Running

# Plugin (loaded by happyDomain at startup)
make plugin

# Standalone HTTP server
make && ./checker-caa -listen :8080

Out of scope for v1

  • CAA parameter matching (;account=…, ;validationmethods=…): only the base issuer domain name is compared.
  • issuemail / issuevmc policies: the consumed TLS observations are for web-PKI endpoints only.
  • HTML report page.