Agentless SSH inventory & security-hardening baseline for Linux fleets — one Excel report, one command.
linux_audit.py connects to a list of Linux hosts over SSH, escalates with sudo,
runs a single read-only collection pass per host, and writes a formatted, multi-sheet
Excel workbook: full inventory plus prioritised hardening Findings and CIS-style
PASS/FAIL/WARN checks. It is agentless (nothing is installed on the targets),
reuses your existing SSH setup, and runs hosts in parallel.
Only run this against systems you are explicitly authorised to access.
- Features
- Screenshots
- How it works
- Requirements
- Installation
- Quick start
- Authentication
- Command-line options
- Host list format
- Output workbook
- Active-hosts file
- Helper utilities
- Security considerations
- Troubleshooting
- License
- Agentless — pure SSH +
sudo; nothing installed on targets. One remote bash pass per host using section markers, so each host is one round trip, not dozens. - Reuses your SSH setup — keys/agent/
~/.ssh/config,ProxyJump, or password login viasshpass. - Parallel — audits many hosts at once (
--workers, default 8). - Graceful failure — unreachable/auth-failed hosts are logged to an Errors sheet with the exact reason; the run continues.
- Inventory — IP addresses, distro, kernel/arch, uptime/load, disk usage, listening ports → process, running services, versions of common daemons, and the full installed-package list.
- Hardening Findings — severity-ranked (High/Medium/Low) issues with concrete recommendations.
- CIS-style checks — ~30 benchmark-style PASS/FAIL/WARN checks per host (SSH policy, file permissions, password aging, sysctl network hardening, MAC, firewall, patching, legacy insecure services, …).
- Per-host detail tabs — a one-page posture summary per server.
- Active-hosts output — writes a reusable list of the servers that actually responded.
- Machine-readable remediation plan (optional) —
--plan-out plan.jsonemits structured, safety-annotated fixes for a downstream hardening/patching tool. Seedocs/PLAN_SCHEMA.md. - Safe output — every cell that could be interpreted as a spreadsheet formula is written as text, which prevents Excel "repair" warnings and neutralises spreadsheet formula-injection from untrusted host data.
Findings — prioritised, colour-coded hardening issues:
CIS Checks — benchmark-style PASS / FAIL / WARN per host:
Per-host detail tab — one-page posture summary:
A complete example workbook is in examples/sample_audit.xlsx.
For each host the tool runs (conceptually):
ssh [user@]host— using your key/agent, or a password viasshpass.- Escalate with
sudoand run one read-only collector script that emits data in@@SECTION-delimited blocks. - Parse the sections, derive Findings and CIS results, and add the host to the workbook and to the active-hosts file.
The collector only reads state (it never changes configuration, and does not run
apt update/repo refresh). Update counts reflect each host's existing package cache.
On the machine you run it from (the "controller"):
- Python 3.9+
openpyxl- The system
sshclient sshpass— only if you use password SSH login (--ask-ssh-pass)
On each target: a POSIX shell and standard tools (ip/ss/df/systemctl/
getenforce/dpkg/rpm, etc.). Every probe is guarded, so missing tools are skipped
rather than fatal. The audited account must be able to sudo (password or passwordless).
git clone https://github.com/vikozs/linux-audit.git
cd linux-audit
python3 -m pip install -r requirements.txtOptionally install it as a command:
python3 -m pip install .
linux-audit --help# key-based SSH + passwordless sudo
python3 linux_audit.py -H hosts.txt -o audit.xlsx
# password SSH login + sudo with the same password (one prompt)
python3 linux_audit.py -H hosts.txt -u sa.vko --ask-ssh-pass --sudo-pass-same-as-ssh -o audit.xlsxCopy hosts.example.txt to hosts.txt and edit it first.
SSH login
| Mode | Flags |
|---|---|
Key / agent / ~/.ssh/config (default) |
(none) |
| Explicit key file | -i /path/to/key |
| Password login | --ask-ssh-pass (prompt) or --ssh-pass-env VAR (from env) — needs sshpass |
Privilege escalation (sudo)
| Mode | Flags |
|---|---|
| Passwordless sudo (default) | (none) |
| Password sudo (prompt once) | --ask-sudo-pass |
| Reuse the SSH password for sudo | --sudo-pass-same-as-ssh |
| No escalation (unprivileged) | --escalate none |
Passwords are read from a prompt or an environment variable and passed over stdin — never on the command line or in the process list. One prompt is reused for all hosts (correct for a shared domain/service account).
| Option | Description |
|---|---|
-H, --hosts PATH |
Host list file (required) |
-o, --output PATH |
Output .xlsx (default linux_audit.xlsx) |
--active-out PATH |
Write reachable hosts here (default active_hosts.txt; '' to disable) |
--plan-out PATH |
Also write a machine-readable remediation plan (JSON). Off by default. |
-u, --user USER |
Default SSH user (overridable per line) |
-p, --port PORT |
Default SSH port |
-i, --identity FILE |
SSH private key |
--escalate {none,sudo} |
Escalation method (default sudo) |
--ask-sudo-pass |
Prompt once for the sudo password |
--sudo-pass-same-as-ssh |
Reuse the SSH password for sudo |
--ask-ssh-pass |
Password SSH login via sshpass (prompt) |
--ssh-pass-env VAR |
Read SSH password from an env var |
--ssh-opt OPT |
Extra ssh -o option, repeatable (e.g. --ssh-opt ProxyJump=bastion) |
--host-key-checking VAL |
StrictHostKeyChecking value (default accept-new) |
--connect-timeout N |
SSH connect timeout, seconds (default 10) |
--cmd-timeout N |
Per-host command timeout, seconds (default 120) |
--workers N |
Parallel hosts (default 8) |
--deep |
Also scan SUID/SGID and world-writable files (slower) |
--packages / --no-packages |
Full installed-package inventory (default on) |
--host-tabs / --no-host-tabs |
Per-host detail tabs (default on) |
One host per line. Blank lines and # comments are ignored.
web01.example.com
admin@db01.example.com
10.0.0.5:2222
bastion.example.com # inline comments allowed
Accepted forms: host, user@host, host:port, user@host:port. The -u default
user applies to lines without an explicit user@.
| Sheet | Contents |
|---|---|
| About | Run metadata, script build, sheet guide |
| Summary | One row per host with key facts and colour-coded risk cells |
| Findings | Severity-ranked hardening issues with recommendations |
| CIS Checks | Benchmark-style PASS/FAIL/WARN checks per host |
| Disk | Filesystems, usage, mount points |
| Listening Ports | Proto, local address:port, owning process |
| Services | Running services per host |
| Software Versions | Versions of detected common daemons |
| Installed Packages | Full package inventory (omit with --no-packages) |
| Users & Auth | Login-capable/privileged accounts, UID 0, sudo, password state |
| SSH Config | Effective sshd hardening settings |
| <hostname> tabs | One posture tab per host (omit with --no-host-tabs) |
| Errors | Unreachable/failed hosts and the reason |
The CIS checks are heuristic, benchmark-style checks derived from collected data — useful as a hardening baseline, not a substitute for a certified CIS-CAT scan.
After each run the reachable hosts are written to active_hosts.txt (change with
--active-out, disable with --active-out ''). It is a valid host list — the IP and
distro are trailing comments — so you can feed it straight back in:
# Active hosts as of 2026-07-09 12:00:00 — 78 reachable, 3 unreachable
eu-west2.kosir.loc # 192.168.101.168 Red Hat Enterprise Linux 9.3 (Plow)
...
python3 linux_audit.py -H active_hosts.txt -u sa.vko --ask-ssh-pass --sudo-pass-same-as-ssh--plan-out writes a JSON handoff file for a separate hardening/patching tool.
The workbook is prose for humans; this is structured data for a machine.
python3 linux_audit.py -H hosts.txt -o audit.xlsx --plan-out plan.jsonEach item carries what to change, what was observed, and — most importantly — whether it is safe to apply unattended:
{
"check_id": "3.5.1",
"key": "firewall.active",
"observed": "ufw:Status: inactive",
"expected": "active",
"action": { "type": "service_enable", "unit": "firewalld|ufw" },
"disruptive": true,
"caution": "Enabling a default-deny firewall over SSH will cut your own session unless the SSH port is allowed first."
}Only CIS FAIL/WARN produce items, so a clean host yields an empty list.
Full contract, action types and a worked consumer example:
docs/PLAN_SCHEMA.md. Sample output:
examples/sample_plan.json.
The plan describes your infrastructure's weaknesses in machine-readable form. Treat it as at least as sensitive as the report;
.gitignoreexcludes it.
check_xlsx.py— verify a report contains no formula cells before opening it:python3 check_xlsx.py audit.xlsx
fix_xlsx.py— repair a report made by an older build (converts any formula cells to text, preserving data and formatting):python3 fix_xlsx.py old_audit.xlsx # writes old_audit_fixed.xlsx
- Authorisation. Only audit hosts you are permitted to access.
- Sensitive output. Reports and host lists describe your infrastructure and its
security posture. The provided
.gitignoreexcludeshosts.txt,active_hosts.txt, and*.xlsxso you don't commit them by accident. Store and share reports accordingly. - Credential handling. Passwords come from a prompt or env var and are passed via stdin — never on the command line.
- Host keys. Default
StrictHostKeyChecking=accept-newadds new keys and refuses changed ones. On a trusted internal network with rebuilt/cloned hosts you may prefer--host-key-checking no --ssh-opt UserKnownHostsFile=/dev/null. - Formula-injection safe. All cell text that could be read as a formula is written as text, so a compromised host cannot plant a payload that executes when the report is opened.
sorry, you must have a tty to run sudo — the target has Defaults requiretty in
sudoers. Remove it, or open an issue to request a --tty (ssh -t) option.
Password SSH login needs 'sshpass' — install it (dnf install sshpass /
apt install sshpass) or use key-based auth.
A host lands in the Errors sheet — the message distinguishes DNS failure, timeout
(host down/firewalled), connection refused, and auth failures. Firewalled hosts wait up
to --connect-timeout before failing.
Excel says "Removed Records: Formula from …" — that report was made by an old build.
Run python3 fix_xlsx.py <file> and open the _fixed copy; new reports carry a
Script build tag on the About sheet and print [build …] at startup.
OneDrive/SharePoint — generate the report to a local path, verify with
check_xlsx.py, then move it into the synced folder. Writing directly into a live-
syncing folder (or over a file open in Excel) can cause locked/partial writes.
Released under the MIT License.
Provided "as is", without warranty. You are responsible for ensuring you have authorisation to audit the target systems and for handling the resulting reports securely.
═══════════════════ ✠ ════════════════════
#### #### #### #### ####
# # # # # # # # # #
# # # # # # # # # #
##### ##### ##### ##### #####
# # # # #
# # # # #
#### #### #### #### ####
═══════════════════ ✠ ════════════════════
Five Nines be upon you, and also with you.
Kubernetes · OpenShift · High Availability
HA-llelujah.
A Highly Available Faith. Keep no Pets. Declare thy state. Back up etcd.
The Scriptorium
- ha-llelujah.dev · the book, the music, and the reliquary
- The Music · the sacred discography
- Be Reconciled · take the vow and join the Reconciled
The Parish
- fivenines.church · confession, prayer, and the living parish
- The Liturgical Calendar · the holy days of uptime
- The Tithe · support the Church
The Canon (source)
- church-of-the-eternal-cluster · doctrine, liturgy, and scripture
- eternal-cluster-mcp · the Oracle, an MCP server that diagnoses thy incidents through doctrine
- vK
- Linux Fleet Audit
- Linux Diskspace
- Linux Fleet Harden
- Size OpenShift subscriptions from evidence
- Diagnose stuck PersistentVolumes, safely
An independent parody, built with love for everyone who has been paged at 03:00. Kubernetes is a trademark of the Linux Foundation. OpenShift is a trademark of Red Hat, Inc. Not affiliated with, nor endorsed by, either. They have real SLAs; we only have belief. HA-llelujah.


