Give certificate deletion a grace period #48
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The hazard
cert_manager::reconcile_once's first loop deletes, immediately and unconditionally, every certificate row whose name is absent from the desired set — row, private key and resolver entry together. There is no grace period and no confirmation that the absence is real.That is correct when a name genuinely left the route table. It is destructive when the absence is transient, because the deletion is irreversible: the name must be re-ordered from scratch, against Let's Encrypt's 50-new-certificates-per-registered-domain-per-week limit — the limit
ORDER_SPACINGexists to respect.Two transient causes are known, and both are now guarded at the trigger rather than here:
list_servicesread was passed on as an empty desired set, so one failed read wiped the entire store. Fixed by skipping the pass instead (1402be1).list_certificatesis a different query on a different table, so it can succeed whilelist_servicesfails — aFromRowdecode failure on one malformedaccess_rules/routing_hostsvalue made that persistent rather than transient.reload_routes_configtriggered on a bare mtime change, and a prefix of a TOML document very often still parses, so a truncated read shrank the service table, woke the reconciler and deleted the momentarily-missing names' keys. The next reload restored the services but not the keys. Narrowed by debouncing the watcher until the file settles.Both fixes are at the trigger. Neither makes the deletion itself safe, and the debounce in particular only narrows the window — a writer slower than the settle interval can still be read mid-write.
Proposal
Require a name to be absent for two consecutive passes before deleting it. First pass marks, second pass deletes.
start_reconcilerin memory — no migration. An empty set after a restart means the first pass never deletes, which is also the safer default.reconcile_once's signature grows a&mut BTreeSet<String>of names seen absent, which is why this is a follow-up rather than part of the release: it touches every reconciler test.Not a regression
Pre-v2.0.0 there was nothing to delete this way — certificates were configured per apex via
[[tls]], not derived from the route table, so a disappearing service never removed one. The exposure arrives with derivation.Test to write first
A pass with an empty desired set must not delete on the first call and must on the second.
cert_manager::tests::an_empty_desired_set_deletes_every_certificatecurrently pins the immediate behaviour and would be updated to the two-pass contract.