checker-caa/README.md

101 lines
3.6 KiB
Markdown

# 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:
```json
{
"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` but is **not committed to the repository**.
To fetch or refresh it, run:
```bash
go generate ./checker/
```
This downloads the current CSV from CCADB. No code changes are needed
to pick up a new snapshot: only a re-embed (recompile) is required
after the file is refreshed. Note that the download depends on CCADB
being reachable; `go build` itself has no network dependency.
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
```bash
# Plugin (loaded by happyDomain at startup)
make plugin
# Standalone HTTP server
make && ./checker-caa -listen :8080
```