One inventory of everything that expires, ranked by blast radius
An expiry inventory across certificates, secrets, keys and domains.
Builds a single inventory of everything in an estate with an expiry date, certificates, CAs, secrets, IAM keys, leases and domains, ranked so the certificate on the payment path outranks the one on a staging dashboard. Most of it is discovered; what cannot be is recorded by hand and ranked the same way. The ranking is the product, since any script can list dates.
The problem
Expiry outages have a signature: they are entirely predictable, they are predicted by nobody, and the post-incident action item is always "add a calendar reminder", which lasts as long as its owner stays on the team.
Expiry dates are not hidden. They live in different systems, each owned by different people, each with its own notification mechanism, and none of them produces a single ordered list:
- TLS certificates are in cert-manager, in a load balancer's configuration, in a CDN, and on the one legacy host that was never migrated.
- Intermediate CAs expire too, and take everything below them. Nobody monitors intermediates, because nobody issued them.
- Secrets and API keys are in a secrets manager, in a CI provider's settings, in a partner's portal.
- IAM access keys are in AWS, discoverable by API, and monitored by nobody: the keys never expire, and rotation is a procedure nobody has scheduled.
- Tokens and signing certificates sit in forges, identity providers and artefact registries, each on its own clock.
- Domains are at a registrar, on somebody's credit card, with a renewal notice going to an address that may or may not still be read.
Even where each system warns, the warnings are unrankable. A team receiving forty expiry notifications a quarter cannot tell which of them matters. So the notifications are triaged by whoever reads them, by recency and not by consequence, and the staging dashboard's certificate gets renewed on time while the payment path's does not, because its notification arrived in a busy week.
The failure is not a missing list. It is a missing ordering.
The method
Rank by blast radius. Consequence is inferred from exposure, traffic, ingress class and environment, and it is overridable. A certificate on an internet-facing ingress in the production namespace carrying live traffic ranks above an internal one, which ranks above staging. Where the inference is wrong, for example a low-traffic endpoint that a payment provider calls, an override in the config corrects it, and the correction lives in version control.
Discount deadlines something else is meeting. Where a source can see that renewal is automated and healthy (a Ready cert-manager Certificate, a Cloudflare universal certificate pack, a registrar domain with auto-renew on), the item is labelled renewal=managed and loses 0.25 of blast radius. A stuck renewal gets no penalty; it rises because everything around it moved down.
Discover what can be discovered; record the rest, and rank it identically. Most of the inventory is enumerated by granting read access. Endpoints and domains are recorded. A manual category holds what nothing can discover: a registrar with neither RDAP nor parseable WHOIS, a credential rotated by hand, a code-signing certificate on somebody's laptop. Manual items go through the same ranking as everything else, so the tool degrades to a well-ordered spreadsheet for the parts of the estate no API reaches.
Emit into the systems people already use. A table for the terminal, JSON for scripts, a calendar subscription in the calendar people already open, Prometheus metrics for the alerting that already pages, a standalone HTML report to share, and -fail-within 14 as a CI gate. The commands, from the README:
./bin/expiry-radar -endpoints shop.example.com -domains example.com
./bin/expiry-radar -config expiry-radar.json -format ical -out renewals.ics
./bin/expiry-radar -config expiry-radar.json -format prometheus
./bin/expiry-radar -config expiry-radar.json -format html -out report.html
./bin/expiry-radar -config expiry-radar.json -fail-within 14 # CI gate
./bin/expiry-radar -config expiry-radar.json -only cloudflare # one source
One repository, not two. expiry-radar is the merge of a former tls-expiry-radar and a secret-rotation-calendar. In the README's words, "they are one tool with one code path. Building two would just be two dormant repos." The ranking only works when everything is in one list.
A partial result is never silent. Exit codes: 0 clean, 1 a -fail-within threshold was breached, 2 bad usage or config, 3 partial results with at least one source failed. A source that fails still returns what it managed to read, and the failure goes to stderr, because, as the README puts it, a report that quietly lost a source reads exactly like a clean estate. Naming a source in -only that the config did not enable is an error for the same reason.
The headline results
- The ranking model.
- Keys without an expiry get one from policy.
- What the tests assert.
- What the tests cannot show.
- Discovery reads what clients see.
The full results, with the tables and the reasoning behind each figure, are in the PDF.
What is in the PDF
- The problem — what goes unanswered without it, and who notices first.
- Why the obvious alternative falls short — stated plainly, including where it is the better choice.
- How it works — the method, not a feature list.
- Concrete use cases — with console output quoted from the repository, never reconstructed.
- The methodology behind any number it emits — every term shown, so the figure survives a question.
- What it deliberately does not do — the section most papers leave out.
Part of the Security & supply chain theme.
Get the PDF
This paper already selected; the download starts when you send the form. No follow-up sequence.