fix(traefik): a renewed certificate is old, and list_certificates is bounded (#5864)

A Traefik that hasn't restarted keeps reporting the certificate it
replaced, so codex.fabledsword.com's old one (21 days left) was listed
beside its renewal (81 days) and would have raised an expiry warning on
2026-10-22. A reported certificate whose every name a later-expiring
one covers is now old, on the page, the widget, the overview and MCP.

list_certificates takes `limit` (default 100) with total/truncated, and
says how many served certificates no HTTP router uses (`unrouted`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 16:00:19 -04:00
co-authored by Claude Opus 5.5
parent 622addc09e
commit 398312b8be
4 changed files with 39 additions and 10 deletions
+11 -3
View File
@@ -307,14 +307,22 @@ def current_certs(certs, scrape_seconds: int) -> tuple[list, list]:
"""(served, old): a certificate is served while Traefik still reports it.
Rows are upserted by serial and never deleted, so a renewed or retired
certificate lingers; one missing from the last GONE_AFTER_SCRAPES
scrapes is old (#5254)."""
scrapes is old (#5254). So is one renewed: a Traefik that hasn't
restarted since keeps reporting the certificate it replaced, so one
whose every name a later-expiring reported certificate covers is old
too (#5864); otherwise it reads as expiring while the renewal serves."""
certs = list(certs)
if not certs:
return [], []
newest = max(c.scraped_at for c in certs)
cutoff = newest - timedelta(seconds=GONE_AFTER_SCRAPES * max(int(scrape_seconds), 1))
return ([c for c in certs if c.scraped_at >= cutoff],
sorted((c for c in certs if c.scraped_at < cutoff), key=lambda c: c.scraped_at, reverse=True))
reported = [c for c in certs if c.scraped_at >= cutoff]
names = {c.serial: cert_names(c.cn, c.sans) for c in reported}
renewed = {c.serial for c in reported for n in reported
if n.serial != c.serial and n.not_after > c.not_after and names[c.serial] <= names[n.serial]}
return ([c for c in reported if c.serial not in renewed],
sorted((c for c in certs if c.scraped_at < cutoff or c.serial in renewed),
key=lambda c: c.scraped_at, reverse=True))
def router_hosts(name: str, domains: dict[str, list[str]]) -> list[str]: