Repository navigation
fix: Source Push sent the apex certificate instead of the wildcard (#153) - #156
Merged
Merged
Conversation
) A Managed ACME definition listing both "*.example.com" and "example.com" is stored by Caddy as two separate certificates. findStorageCertificate chose between them on latest NotAfter alone, so the apex — normally the newer one, because it was added to the definition later — won, and Source Push shipped it to the fleet targets. The target then presented a certificate valid only for the bare domain and every subdomain failed TLS, while the source itself kept serving correctly. Selection now follows domain coverage, with expiry only breaking a tie, so the v2.42.0 export feature and renewal pickup keep their behaviour for a single-name definition. A wildcard scores above a concrete name because it protects every subdomain behind it; otherwise the wildcard and the apex each cover exactly one requested name and the expiry tie-break reintroduces the bug. One target certificate row holds one keypair, and fleet_deployments is keyed one target row per source certificate, so a single push cannot carry both certificates. Rather than fail silently, the names the chosen certificate does not present are recorded on the source's activity log with the fix: give them their own certificate definition and push targets.
|
Claude finished @X4Applegate's task in 18s —— View job Review of #156 (already merged)The fix is correct. I found no blocking issues.
Minor, non-blocking
The PR body says there is no CHANGELOG entry because it belongs to the release commit. That is fine as long as the v2.68.6 release commit adds one. |
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #153.
Symptom
A Managed ACME certificate definition listing both
*.example.comandexample.compushes the wrong certificate to its fleet targets. After aSource Push the target presents a certificate valid only for the bare apex, so
every subdomain fails TLS with a name-mismatch error — while the source node
itself keeps serving all names correctly, which makes it look like a
target-only or network problem.
Root cause
Caddy stores
*.example.comandexample.comas two separatecertificates in its storage tree, because a wildcard SAN does not cover its
own apex.
findStorageCertificateininternal/server/certificate_export.gochosebetween stored candidates on latest
NotAfteralone. The apex is normallythe newer of the two — it gets added to an existing wildcard definition later —
so it expires later, won the comparison, and was handed to Source Push. The
exported keypair therefore presented only
example.com.Expiry was a reasonable tie-break when a definition mapped to one stored
certificate; it was never a correct way to decide which name set to ship.
Fix
Selection now follows domain coverage, with expiry only breaking a tie:
certificateCoversNamematches a SAN against a requested name the way TLSdoes, including single-label wildcard matching, and deliberately does not
treat
*.example.comas cover for a bareexample.com.storageCertificateCoveragescores a candidate against the requested namesand returns the ones it does not present. A wildcard name scores 2, a
concrete name scores 1 — see the proof below for why that weighting is
load-bearing rather than cosmetic.
findStorageCertificatepicks the highest score, falling back to latestNotAfteron a tie, so the v2.42.0 export feature and renewal pickup keeptheir exact previous behaviour for any single-name definition.
A target certificate row holds one keypair, and
fleet_deploymentsis keyed onetarget row per source certificate, so one push structurally cannot carry both
certificates. Rather than drop the uncovered names silently, they are recorded
on the source's activity log (
internal/server/fleet_sync.go) with theremedy: give them their own certificate definition and push targets.
Tests and the proof they fail without the fix
New:
internal/server/certificate_storage_coverage_test.go,internal/server/fleet_sync_wildcard_push_test.go.Proof 1 — revert the selector to the old latest-
NotAfter-only behaviour:The second failure message is the reported bug verbatim: the pushed certificate
presents only
[example.com].Proof 2 — keep coverage scoring but remove the wildcard weighting (score
+1for every covered name instead of+2for a wildcard):This is the important one. Coverage scoring alone does not fix the bug: for
a
*.example.com+example.comdefinition the wildcard and the apex eachcover exactly one of the two requested names, so it is a 1–1 tie and the expiry
tie-break re-selects the apex. The wildcard must outweigh a concrete name —
because it protects every subdomain behind it — or the fix silently regresses.
Please keep that weighting if this code is refactored.
With the fix in place,
go test ./...andgo vet ./...pass on this branchwith a clean tree.
Review notes
main(a2735a9, = v2.68.5) so this is a single standalonecommit. It was originally developed stacked on
fix/issue-151([Bug]: Stale "Issuance error" persists after successful ACME renewal information update #151); thetwo bugs are unrelated and touch disjoint files, so the rebase was clean and
verified to produce an identical tree. Reviewable and mergeable in either
order.