=WHOSTILL(has.access)
1
lfu.kosir.info · agentless · MIT

linux-users

No soul is struck from the roll. Only locked.

Who may enter, who may become root, and whose key is still in a file two years after they left. Audit local accounts, sudoers and authorized_keys across the fleet — then lock the stale ones, with live re-validation first.

“And his key was in the file, and he had left in the spring of the year before.” Findings 6:3 — the jump host, of course

Offboarding removes someone from the directory, the chat, the wiki and the badge system, and then stops. The account on the jump host was created by hand in a hurry in 2022, so it is in no directory, and nothing will ever tell thee it is still there.

jump01.hostname.loc · click an account to lock it

rootuid 0 · protected, always
local.userthe account thou art connected as · protected
m.horvatuid 1231 · last login 617 days ago · stale
j.novakuid 1240 · last login 402 days ago · stale
temp.audituid 1288 · never logged in · reported, never auto-locked

Try root, or the account thou camest in on. Both decline. So does the one that has never logged in — and that last refusal is the one worth understanding.

2

The Roll

Three verbs. Only one of them touches a host.

discover — read-only. Enumerates accounts from passwd, joins password status and ageing from shadow, and last-login age from lastlog. Reads /etc/sudoers and sudoers.d, and every per-user authorized_keys it can reach. Writes users_plan.json and a workbook.

apply — takes the plan, re-runs discovery first, and locks only the accounts that are still stale. Anyone who logged in since the audit simply drops out of the list. It never runs userdel, never removes a key, never edits sudoers.

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

local.user@localhost $ linux_users.py discover -H hosts.txt --stale-days 90
88 hosts · 1,412 accounts
stale 71 login accounts, no login in 90+ days
never 18 accounts that have never logged in (reported only)
nopasswd 23 sudoers grants with NOPASSWD
issues 2 duplicate UID 0 · 4 empty passwords
weak keys 6 ssh-dss entries
users_report.xlsx written. nothing was changed.

What makes an account stale. It must be a login account — UID >= 1000 with a real shell — whose last login is older than --stale-days, and which is not protected. Every one of those four conditions is doing work.

3

Who Is Never Touched

Re-checked at apply time, as a second guard

ProtectedWhy
root and every UID < 1000 System accounts. Locking one is how a service stops at 04:00 with no obvious cause.
The account thou connectest as (-u) The oldest mistake in fleet management, prevented structurally rather than remembered.
Anything named with --protect NAME Repeatable. For the service account with a human-looking name that has not logged in since it was created, and must not be.
Last login unknown, or never The important one. See below.
Never logged in is not the same as stale

An account with no login history is reported and never auto-locked. Absence of evidence is not evidence of disuse: lastlog can be truncated, rotated, or simply never written for an account that authenticates some other way. Locking on a missing record would mean locking on a gap in a file, which is not a finding — it is a guess.

Thou wilt still see all eighteen of them in the report, because they are worth thy attention. The tool just will not act on them for thee.

4

Locked, and Optionally Expired

Never deleted — there is no verb for it

There is no userdel in this tool. Not behind a flag, not behind a confirmation, not in the plan schema. Deletion is a decision with no undo; it stays with thee.

What apply doesCommandWhen
Locks the passwordusermod -L Always. Immediate, and reversed with a single command if thou art wrong about who left.
Expires the accountchage -E Only with --expire. This is the one that also stops key-based login, which locking the password alone does not.
SSH keysreported Never modified. Editing authorized_keys automatically across a fleet is a very efficient way to lock out an entire team at once.
sudoersreported Never edited. A malformed sudoers file is a host nobody can escalate on, including thee.
Home directory and owned filesuntouched Somebody's crontab, somebody's script, somebody's only copy of something.

Worth knowing: locking the password alone does not stop a key-based login. If the account has a key in authorized_keys — and the stale ones usually do — thou wantest --expire as well.

And, as everywhere in this family, it asks first:

jump01.hostname.loc: [account] lock m.horvat last login 617d ago [account] lock b.kralj last login 288d ago [account] dropped j.novak — logged in 3 hours ago [account] dropped temp.audit — no login history, never auto-locked Apply 2 action(s) on jump01.hostname.loc? [y/N/q]

The two dropped lines are the re-validation earning its keep. The plan was written last week. One of those people came back from parental leave this morning, and the other never had a login record to begin with.

5

What the Workbook Says

Eight sheets, and the flags matter more than the counts

SummaryAccountsStale CandidatesSudoers SSH KeysIssuesErrorsAbout

Stale Candidates is the sheet apply would act on — read it before thou runnest anything. Sudoers lists grant entries with NOPASSWD highlighted. SSH Keys is the per-user key inventory with weak types — ssh-dss — called out. Issues collects duplicate UID 0, duplicate UIDs, and empty passwords: three findings that are each, on their own, worth a conversation.

Alongside stale, discovery flags accounts with never-expiring passwords, accounts that have never logged in, NOPASSWD sudo grants, and weak keys. None of those make an account stale. All of them belong in the report.

Hashes are never collected

It reads shadow for status and ageing fields only. There is nowhere in the code where a password hash is read, stored, or written to a report, because a fleet audit tool holding eighty hosts' worth of hashes is a liability wearing a spreadsheet.

6

The Rite of the Roll

Count them, read the sheet, then lock the ones thou art sure of

# receive it git clone https://github.com/vikozs/linux-users.git cd linux-users python3 -m pip install -r requirements.txt
# count them — read-only, mutates nothing python3 linux_users.py discover -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh \ --stale-days 120 --protect deploybot
# lock — re-runs discovery first, asks per host python3 linux_users.py apply --plan users_plan.json \ -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh
# lock and expire — the one that also closes key-based login python3 linux_users.py apply --plan users_plan.json \ -H hosts.txt -u local.user \ --ask-ssh-pass --sudo-pass-same-as-ssh --expire

Sudo is needed to read shadow and other users' authorized_keys. Auth flags match every other book in the family: keys or agent by default, --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 read an authorized_keys file on a slow afternoon and did not enjoy what was in it. Agentless, parallel, MIT licensed. Hosts it cannot reach go to their own sheet with the reason, and the run carries on.

Its plans can also be handed to linux-apply, whose users adapter guards each lock on “the account still exists and is not currently logged in”, and drops root and UID 0 defensively on the way through — a second refusal behind the first.

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 GECOS field 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-drift, which notices when a new account appears without a ticket.

Kneel also at the parish and read at the scriptorium. The Cluster abides. The Loop reconciles. The key, unremoved, still opens the door.

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.