Skip to content

refactor(ingress): one CAA flow for every dns-01 setup - #167

Merged
kvinwang merged 1 commit into
mainfrom
refactor/ingress-caa-flow
Oct 16, 2026
Merged

kvinwang merged 1 commit into
mainfrom
refactor/ingress-caa-flow

Conversation

@kvinwang

Copy link
Copy Markdown
Collaborator

Supersedes #166.

Why

CAA handling differed per setup, and SET_CAA meant different things:

write CAA check CAA before issuing retry
plain dns-01 if SET_CAA no if new account and SET_CAA (#159, #163)
delegation always, SET_CAA ignored via dnsguide if new account

The retry existed only because the account was created by the first order, too late for the CAA to name it.

What

Every dns-01 pass: register the account (certman.py register) → publish records, with the CAA pinned to the account when SET_CAA (at the name, or in the delegation zone) → check the CAA in force (dnsguide.py --caa-only, exit 3 only on a definite refusal) → issue once.

  • SET_CAA defaults to true under DELEGATION_ZONE; false there now works, with a warning that anyone who can write the delegation zone can get certificates.
  • A failed CAA write fails the pass in both setups (plain dns-01 used to issue without it).
  • certman re-obtains a lineage when the account it records is gone (an account now always exists before renewal).
  • tls-alpn-01 already worked this way (register → print → check → issue) and is unchanged.
  • README, TESTING.md, DNS_PROVIDERS.md updated.

Tests

  • Unit tests 79 (new: --caa-only, replaced-account re-obtain); test_sanitizers.sh; shellcheck nothing new.
  • process_domain stubbed: plain with/without SET_CAA, wildcard, delegation with default and SET_CAA=false, CAA refusal, failed registration — each runs the expected steps.
  • Pebble + mock-cf-dns end-to-end: results will follow in a comment.

match = re.match(r"\s*account\s*=\s*(\S+)", line)
if match:
recorded = match.group(1)
except OSError:
@kvinwang
kvinwang force-pushed the refactor/ingress-caa-flow branch 3 times, most recently from 0403227 to 7714c81 Compare October 16, 2026 02:53
CAA handling had grown a branch per setup. Plain dns-01 issued, wrote the
CAA if SET_CAA, then retried under conditions (#159, #163); delegation
ignored SET_CAA and always wrote it; neither checked the CAA in force
before asking the CA, except delegation through dnsguide. The retry
existed only because the account was created as a side effect of the
first order, so the CAA could not name it in time.

Now every dns-01 pass runs the same steps:
1. register the ACME account on its own (new `certman.py register`);
2. publish the records, including the CAA pinned to that account when
   SET_CAA is on -- at the name, or in the delegation zone;
3. check that the CAA in force lets this account issue (new
   `dnsguide.py --caa-only`; exit 3 only on a definite refusal), so a
   refusal the CA would give does not cost a failed validation. With
   SET_CAA the record was just written and resolvers may still cache the
   one it replaced, so the check waits up to DNS_SETUP_TIMEOUT for it;
   otherwise it checks once. This is the only CAA gate in dns-01:
   dnsguide's record check no longer also checks CAA unless asked, as
   tls-alpn-01 still does;
4. issue once.

SET_CAA means the same thing everywhere. It defaults to true under
DELEGATION_ZONE, where anyone else who can write the delegation zone
could otherwise pass the delegated challenge, and setting it false
there now works, with a warning. A failed CAA write fails the pass in
both setups rather than issuing without the pin that was asked for.

Since an account now always exists before renewal, certman replaces a
lineage when the account it records is gone, instead of when no
account exists at all.

Signed-off-by: Kevin Wang <wy721@qq.com>
@kvinwang
kvinwang force-pushed the refactor/ingress-caa-flow branch from 7714c81 to a89d1c8 Compare October 16, 2026 02:56
@kvinwang

Copy link
Copy Markdown
Collaborator Author

End-to-end, PR scripts mounted on the 2.8 image (dstack-ingress:2.8-rc), dstack tools/mock-cf-dns (Cloudflare API + DNS, plus a DoH shim serving its records with configurable cache lag), Pebble validating DNS-01 against it, dstack-simulator v0.6.0. Final code 7714c81 (later amend is a comment only):

Case Result
Fresh, SET_CAA=false register → alias/TXT → CAA check → 1 certonly; no CAA written
Fresh, SET_CAA=true, x + *.x account registered, issue/issuewild pinned to it before the first certonly; 1 order per name, no retry
Restart no register, no certonly, renew not due, no reload
Stale CAA pinned to another account, SET_CAA=false CAA forbids issuing … every pass, backoff, 0 certbot runs for it; other domain unaffected
Same, SET_CAA=true, DoH lagging record rewritten, check WAITs ~20s, then 1 certonly; no failed pass
issue "digicert.com" at parent blocked, 0 certbot runs
DoH unreachable warning, issuance proceeds
Delegation (wait mode): default / SET_CAA=false / stale CAA with each CAA in delegation zone before the order / warning, CNAME kept, no CAA / fails fast in ~1s / overwritten and issued once
Upgrade from 2.8 state; then account dir deleted kept and renewed; new account registered, lineage re-obtained once, renewal conf records the new account, later passes not due
CA unreachable on fresh state registration fails, 0 orders, exit 1
tls-alpn-01, wait mode records incl. CAA verified, issued
Unit tests / sanitizers 82 OK / 78 PASS

Pebble does not enforce CAA, so refusals are judged by the container's check.

@kvinwang
kvinwang merged commit c87f271 into main Oct 16, 2026
6 checks passed
@kvinwang
kvinwang deleted the refactor/ingress-caa-flow branch October 16, 2026 03:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants