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:
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user