Admin host is excluded from wildcard certificate matching, so it refuses the handshake #52
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?
Tested on
:acmearr(post-40c87a7,[[acme]]array form) against Let's Encrypt staging via a real dns01proxy account, isolated on a scratch network. Not reproduced against production LE.What happens
With a wildcard route host covering the zone, an arbitrary unconfigured subdomain is served the wildcard, but the configured
[[admin]] hostis refused.The wildcard order succeeds:
SNI probes against the running router:
grafana.azula.oates.wssubject=CN=*.azula.oates.wsdeep.azula.oates.ws(not in any config)subject=CN=*.azula.oates.wsrouter.azula.oates.ws(the admin host)tlsv1 alert access deniedWhy this looks like a specific exclusion rather than a lookup gap
deep.azula.oates.wsappears nowhere in the config and is still served the wildcard, so the wildcard lookup-candidate path is generic and working.router.azula.oates.wsmatches*.azula.oates.wsby exactly the same rule and is refused — so the admin host appears to take an earlier path that refuses before the generic candidate lookup is reached.Separately, the reconciler orders the admin host as its own name rather than treating it as covered:
(The 403 is my dns01proxy account being scoped to
allow_domains = ["azula.oates.ws"], which permits the wildcard's_acme-challenge.azula.oates.wsonly. That scoping is correct and deliberate — a DNS-01 account that can write arbitrary_acme-challenge.<anything>records is a much broader grant. But it means the per-name order can never succeed, and it retries in a loop roughly every 60s.)Impact
A deployment using one wildcard — which is the shape that minimises LE rate-limit exposure, and the only shape a narrowly-scoped dns01proxy account permits — has no working HTTPS name for the admin dashboard.
hostis singular, so there is no second name to fall back to.What I think is wanted
A name that matches a configured wildcard route host should be (a) served that certificate at handshake regardless of whether it is a service host or the admin host, and (b) not ordered separately. If the admin host is deliberately held to a stricter rule, the refusal needs a log line saying so — currently it is silent at the TLS layer and the only signal is the unrelated-looking 403 retry loop.
Possibly related: #21 (new ACME account per attempt) still reproduces here —
created new accountwas logged separately per name.Fixed on main in
a55bd0a+e51f080. Not an admin-path exclusion — admin hosts are chained intodesired_nameslike any other, and a[[services]]host with the same spelling reproduces it identically. Two independent defects stacked:1. A wildcard route host did not cover the names beneath it.
desired_namescopied every route host into the wanted set verbatim, so*.azula.oates.wsandrouter.azula.oates.wswere two unrelated strings and ER ordered a second certificate for a name the wildcard already certifies. Your admin host happened to be the one spelled out, which is why it looked admin-specific. Now a name a configured wildcard covers is dropped from the set — one label, never the apex (RFC 6125). That also stops the duplicate spending weekly issuance budget, which is the other reason Caddy made the same collapse its default in 2.10.2. A tombstone shadowed a wildcard that covered the name. When the redundant order failed,
lookupreturnedNoneat the first candidate holding a tombstone and never reached*.azula.oates.wsin the next slot. That read #38 too broadly: #38 forbids substituting a different identity — the bare parent's single-name certificate, or the self-signed fallback — and a wildcard over the name's parent genuinely certifies the name. Withholding it was the defect. The tombstone now records the refusal without stopping the walk; if no candidate holds a certificate the lookup still returnsNoneand still never reaches the fallback.That second fix is why
deep.azula.oates.wsworked whilerouter.azula.oates.wsdidn't: the unconfigured name had no entry to stop at, so it fell through to the wildcard — which is what should have happened to the admin name.Both fixes are independent on purpose. The collapse means this shape no longer arises from config alone, but a tombstone can still shadow a wildcard whenever a wildcard route is added after a name has already failed.
The end-to-end rustls test was widened rather than relaxed: the tombstoned name covered by the wildcard must now complete, and a second tombstoned name covered by nothing must still fail — with the fallback carrying that name and trusted by the client, so the refusal can't pass for want of anything to serve. 341 tests green, clippy clean, in
rust:1.98.0-bookworm.Worth re-testing on
:developonce CI publishesd3d7d1f. #21 is untouched and still open — with the collapse in placerouter.azula.oates.wsis no longer ordered at all, so it shouldn't block you either way.