=DAYSLEFT(thy.certs)
1
lfc.kosir.info · agentless · MIT

linux-certs

Every certificate knows the hour of its death. Thou dost not.

It reads a list of hosts over SSH, finds the certificates, and reports how long each has left — plus the ones that were weak the day they were issued.

“The certificate was valid until Sunday. It was Sunday.” Findings 7:2 — recorded during a change freeze

An expiry is the only outage thou art told about in advance, in writing, months ahead, by the thing that will cause it. And still it happens, because nobody read the notice. This reads the notice. All of them, on every host, at once.

local.user@localhost $ linux_certs.py scan -H hosts.txt
88 hosts · 214 certificates · trust bundles skipped
web01.hostname.loc nginx server cert expired 3d ago
cert_report.xlsx + cert_inventory.json written. nothing was changed.
2

The Vigil

Two verbs, and neither of them touches a host's state

This is the most restrained book in the family. It has no apply, and there was never going to be one. It will not renew, will not reissue, will not write a byte to a target. Renewal is CA-specific and deliberately out of scope: the tool tells thee what is expiring, and thy CA tooling acts on it.

scan — read-only, full stop. Writes cert_inventory.json and cert_report.xlsx.

report — re-renders an existing inventory to Excel without going near the fleet at all. For the second meeting about the same spreadsheet.

Parsing runs on the target with openssl, which is guaranteed present on RHEL, so this side only ever handles simple records. For each certificate it takes subject, issuer, serial, validity dates, days-left, key type and size, and signature algorithm.

Signal, not noise

Trust-store bundles (ca-bundle*) are skipped by default. A fleet report that opens with one hundred and thirty shipped CAs per host is not a report, it is a haystack with thy leaf certificate somewhere in it. Name a trust-store path with an explicit --root if thou really wantest the hay.

Sudo is used only to read root-owned certificate paths. If thine are world readable, drop it entirely with --escalate none.

3

Where It Looks

A set of roots by default, and wherever else thou pointest it

The default roots are the RHEL service and leaf certificate directories:

/etc/pki/tls/certs · /etc/httpd · /etc/nginx · /etc/pki/dovecot · /etc/pki/ipa

Add thine own with --root, which is repeatable. Multi-certificate bundles are expanded, so chain certificates are seen individually rather than counted once and forgotten.

Ports, if thou askest. --probe-ports additionally connects to local listening TLS sockets with openssl s_client to see what is actually served, which is not always what is on disk. The probe stays on loopback, on the target, by design. It does not reach across thy network, and it does not become a scanner.

PEM, in this release

PEM certificates are parsed. DER-only files are not decoded. Said here rather than left for thee to discover from a suspiciously short report.

4

The Book of Hours

Three verdicts, and one threshold thou controllest

The colours are not decoration. They are the same ones the workbook writes, so the page and the report agree about what is on fire.

HourVerdictWhat it means for thee
Already past expired The service is either already failing, or is being trusted by something that stopped checking. Both are worse than they sound.
Inside the threshold
--warn-days, default 30
expiring Sorted soonest-first on its own sheet, because this is the only column anyone will read on a Monday.
Beyond the threshold valid Listed with its exact date anyway. Today's comfortable certificate is next quarter's incident.

Thirty days is the default because thirty days is roughly one change window plus one round of asking. Widen it with --warn-days 45 when thy change process is slower than thy CA.

Weak material

An expiry date is only half the question. The other half is what the certificate was made of, and that half has no deadline to remind thee. Two things are flagged:

Undersized keys. An RSA or DSA key too small to still be defensible, named with the host and the path. These do not stop working on a particular day; they simply remain, quietly, until an auditor finds them or somebody else does.

Retired signatures. SHA-1 and MD5 signature algorithms, flagged the same way. Modern clients have refused these for years, which means the ones still in service are the ones nothing modern talks to — which is its own kind of finding.

Weak Material
web01.hostname.loc /etc/pki/tls/certs/legacy.crt RSA 1024 issued 2016
app02.hostname.loc /etc/nginx/ssl/internal.crt sha1WithRSA issued 2018
api03.hostname.loc /etc/pki/tls/certs/service.crt RSA 1024 CN=localhost

CN=localhost, on a production API host, presented to real clients. This one is not invented. It is on somebody's fleet right now, quite possibly thine.

5

A Report Thou Canst Hand to Anyone

The auditor does not want thy terminal scrollback

A formatted workbook and a machine-readable inventory, written side by side. The workbook is for the meeting; cert_inventory.json is the same data, and is what report reads back.

SummaryCertificatesExpiring Soon Weak MaterialErrorsAbout

The Errors sheet matters more than it looks. A host that refused the connection, or one that simply has no openssl, goes to its own row with the reason — and the run carries on through the other eighty-seven. A fleet tool that stops at the first locked door is a single-host tool with ambitions.

About records the tool version, the build stamp, the threshold that was in force, and the totals — so a report read six months later still says what it was measuring.

6

The Rite of the Vigil

Receive it, then look. There is no third step.

# receive it git clone https://github.com/vikozs/linux-certs.git cd linux-certs python3 -m pip install -r requirements.txt
# look — read-only, mutates nothing, ever python3 linux_certs.py scan -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh
# a longer horizon, and what is actually served python3 linux_certs.py scan -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh --warn-days 45 --probe-ports
# and thine own directories, repeatable python3 linux_certs.py scan -H hosts.txt -u local.user --ask-ssh-pass \ --root /etc/pki/tls/certs --root /opt/app/pki

Auth flags match every other book in the family: keys or agent by default (-i ~/.ssh/id_ed25519 for an explicit key), --ask-ssh-pass for password login, --sudo-pass-same-as-ssh for sudo. Passwords travel by stdin or the SSHPASS environment variable, never argv.

7

What This Actually Is

Breaking the fourth wall, briefly

A real tool, written by a real sysadmin who once spent a Sunday finding out that the certificate nobody owned was the one everything depended on. Agentless, parallel, MIT licensed.

It parses with openssl on the target and writes nothing there. Private keys are never read, never transferred, and never touched — only the public certificate is opened, because that is all the question requires.

It writes an xlsx, and it never writes a formula into one. Everything a host reports is written as text, so a compromised server cannot hide a payload in a certificate subject and have it fire when thou openest the report. Same as its elder siblings, through the same xlsx_safe module. That part is still not a joke.

The test fixtures are real certificates, minted with precise validity dates and run through the actual collector script — so the shell scanner and the parser are tested together rather than separately and hopefully.

Built by vK, who has been paged at 03:00 and did not enjoy it. Companion to linux-audit — the one that found the ninety-seven percent — and linux-harden, which mends what the audit finds. This one only watches the clock.

Kneel also at the parish and read at the scriptorium. The Cluster abides. The Loop reconciles. The certificate, unattended, expires.

8

The Communion of Tools

Nothing here works alone, and neither shouldst thou

The fleet is one testament in several books. Each does one job, writes a report thou canst hand to anyone, and refuses to be clever with thy servers.