=DIFF(then, now)
1
lfd.kosir.info · agentless · MIT

linux-drift

Thou wert hardened once.

Capture a baseline of the state thou carest about. Then check later runs against it and report what moved — effective sshd config, sysctl, file hashes, packages, ports, users, groups.

“And the host was compliant in March, and no one had checked since.” Findings 11:4 — the week before the audit

Drift is rarely malice. It is a Tuesday, and a deadline, and a colleague who meant well and needed the service up before the demo. Nobody logs it, because nobody thinks of it as a change. It is simply the distance between the state thou declaredst and the state thou hast.

drift_baseline.json · captured 2026-03-14 · compared just now

- sshd PermitRootLogin no → now yes
- sysctl net.ipv4.conf.all.rp_filter = 1 → now 0
- file /etc/sudoers sha256 3f9c… → now a1e7…
+ package telnet installed since the baseline
+ port 0.0.0.0:8080/tcp listening since the baseline
↑ click to reconcile — though this tool never does that itself
2

The Fall

Nobody drifts on purpose, which is exactly the problem

A hardened fleet is a photograph, not a state. The photograph was true. Then the year happened.

The temporary sudoers file. Granted for one afternoon in the spring, to get a deploy over the line. The afternoon ended. The file did not, and the person it was for now works somewhere else.

The sysctl that came back. Set correctly by the hardening run, then quietly reverted by a package postinstall, or by a reboot that read a config nobody updated. No alert fired, because nothing was watching a kernel parameter.

The drop-in that overrode the fix. Somebody added a file to sshd_config.d and it now wins the parse order. The main sshd_config still reads correctly to anyone who opens it. The effective configuration does not.

The host that missed the run. Unreachable during the hardening window, so it was never hardened at all. It has been drifting from a baseline it never had, and it is the one running the thing that matters.

None of these are incidents. Each is a small, reasonable decision. Collectively they are why the audit finds most of what it found last year.

3

The Baseline

Three verbs. None of them writes to a target.

This is a read-only book, entirely. The only things it writes are the local baseline and report files. It never changes a host, and it has no flag that makes it. Remediation is linux-harden's job, working from a linux-audit plan; this is the “has anything moved since we hardened it” tool, and the moment a drift detector is allowed to fix things it stops being a detector and becomes an argument.

capture — connects over SSH, reads the watched categories, and writes drift_baseline.json to thy workstation. One snapshot per host. Do this immediately after a hardening run, while the fleet is still telling the truth.

check — reads the same categories today and diffs each host against its own baseline. Writes a workbook and drift_report.json.

report — re-renders an existing report to Excel without touching the fleet.

local.user@localhost $ linux_drift.py check -H hosts.txt --baseline drift_baseline.json
88 hosts · baseline 2026-03-14 · 143 days elapsed
61 hosts unchanged
24 hosts drifted (112 items)
2 hosts unreachable → Errors sheet, run continues
1 host no baseline — never captured
drift_report.xlsx written. nothing was changed.

The last line is the one worth reading twice. A host with no baseline has not drifted — it has never been anywhere to drift from. It goes to the Errors sheet alongside the unreachable ones, because both mean “this host is not covered”.

The golden host

Self-capture is the default: each host is diffed against its own snapshot. --golden with --golden-host instead holds the whole fleet to one reference host's configuration — for fleets that are supposed to be uniform and, quietly, are not. Different question, same machinery.

4

What Is Watched

Seven categories, chosen with --watch, all of them by default

CategoryRead fromWhy it earns its place
sshd sshd -T The effective config, so drop-in precedence in sshd_config.d is reflected rather than guessed. Reading the main file alone is how a host looks hardened and is not.
sysctl sysctl A curated set of hardening-relevant parameters, deliberately curated to leave out volatile counters. A drift report full of changing packet counts is a drift report nobody opens.
files mode, owner, group, SHA-256 Security-relevant /etc files: passwd, shadow, group, login.defs, sudoers, sshd_config and its drop-ins, crontab, hosts. Contents are never collected — only the hash.
packages installed names Additions and removals only. Version changes are linux-patch's domain, and mixing the two makes both reports useless.
ports listening TCP/UDP A port that was not listening in March and is listening now is either a deployment or a problem, and thou wantest to know which before someone else does.
users name → id map A new account that arrived without a ticket.
groups name → id map Group membership is where privilege quietly accumulates, and it is the one nobody reviews.

If a tool is missing on a host — no sshd, no ss, no rpm — that category comes back empty rather than failing the host. Watch a subset with --watch sshd,sysctl,files when thou carest about one thing and want the report short enough to read.

5

What the Workbook Says

Six sheets, and the changes are split by how thou wilt act on them

SummaryDrifted ItemsPackage Changes Port ChangesErrorsAbout

Drifted Items carries every sshd, sysctl, file, user and group change, each with was and now side by side. Packages and ports get their own sheets because they are lists rather than value changes, and because they are the two thou wilt most often hand to somebody else.

Summary gives, per host, the drift item count, the package and port change counts, and whether a baseline existed at all. Errors holds unreachable hosts and hosts with no baseline. About records the mode — self or golden — and which categories were watched, so a report read six months later still says what it was measuring.

Run check on a schedule and mail thyself the summary. Drift found in the week it happened is a conversation; drift found the week before an audit is a project.

6

The Rite of Remembrance

Capture while it is true, compare while there is still time

# receive it git clone https://github.com/vikozs/linux-drift.git cd linux-drift python3 -m pip install -r requirements.txt
# capture — do this the day after hardening, not the month after python3 linux_drift.py capture -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh -o drift_baseline.json
# check — read-only, writes a report python3 linux_drift.py check -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh --baseline drift_baseline.json
# or hold the whole fleet to one golden host's configuration python3 linux_drift.py check -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh --baseline drift_baseline.json \ --golden --golden-host web01.hostname.loc

Sudo is used to read sshd -T and root-owned files. Auth flags match every other book in the family. 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 hardened eighty servers, felt good about it for four months, and then looked. Agentless, MIT licensed. Hosts it cannot reach go to their own sheet with the reason, and the run carries on.

Keeping it read-only was a decision, not a limitation. A detector that can also act will eventually be trusted to act, and then nobody is detecting anything — they are just running a config manager with a worse interface.

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 package name 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.

Built by vK, who has been paged at 03:00 and did not enjoy it. Companion to linux-audit, which examines, and linux-harden, which absolves. This one only remembers what thou wert.

Kneel also at the parish and read at the scriptorium. The Cluster abides. The Loop reconciles. Thy fleet, unwatched, wanders.

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.