No description
  • Go 97.5%
  • Makefile 1.5%
  • Dockerfile 1%
Find a file
2026-04-23 02:53:46 +07:00
checker Initial commit 2026-04-23 02:53:46 +07:00
plugin Initial commit 2026-04-23 02:53:46 +07:00
.gitignore Initial commit 2026-04-23 02:53:46 +07:00
Dockerfile Initial commit 2026-04-23 02:53:46 +07:00
go.mod Initial commit 2026-04-23 02:53:46 +07:00
go.sum Initial commit 2026-04-23 02:53:46 +07:00
LICENSE Initial commit 2026-04-23 02:53:46 +07:00
main.go Initial commit 2026-04-23 02:53:46 +07:00
Makefile Initial commit 2026-04-23 02:53:46 +07:00
NOTICE Initial commit 2026-04-23 02:53:46 +07:00
README.md Initial commit 2026-04-23 02:53:46 +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 (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.