root@sudosecurity:~/hardline/guides $ cat guides .md
Hardline guides
Hardline recipes for real setups: production servers, Docker hosts, phone alerts, fleets, CI gates, Prometheus and Grafana, auditor evidence and file permissions.
The getting started page gets one server hardened. These are for real life: the box that's been in production for three years, the Docker host, the twenty servers you'd rather not SSH into one by one.
Hardening a server that's already in production
A live box deserves a slower pace. Here's how I'd do it:
- Audit and read it.
sudo hardline audit, thenhardline explain <id>on anything you're not sure about. - Save a baseline.
sudo hardline report --out /root/before.html, so you can show the before and after. - One phase at a time, outside peak hours. Preview each phase and read the diff. For SSH and the firewall, give yourself more room with
--timeout 300(up to 900 seconds). - Accept what you have to keep. If an app really needs something Hardline flags, accept it with a reason and a review date. Then
applyleaves it alone, and the grade still tells the truth. - Turn on watch so you hear about it when someone undoes your work.
Running apps keep working. Phase 2 keeps every port that's already listening open, and phase 1 refuses to run if you'd have no key-based way back in.
Docker hosts
Docker writes its own iptables rules, so a published container port (-p 8080:80) can be reachable even when ufw says it's blocked. Phase 6 flags exactly that, flags a Docker API listening on the network without TLS (anyone who can reach it effectively has root), and tightens the daemon defaults.
sudo hardline audit --phase 6
sudo hardline apply --phase 6 --commit
The fix for exposed ports is on your side: bind them to localhost (-p 127.0.0.1:8080:80) and put a reverse proxy in front. hardline explain walks you through it.
Alerts on your phone
The quickest setup is ntfy: install the app, subscribe to a hard-to-guess topic, then:
echo "https://ntfy.sh/your-long-random-topic" | sudo hardline watch notify set
sudo hardline watch enable --schedule hourly
The URL is read from stdin, so it never ends up in your shell history. A test alert has to go through before it's saved. From then on, any regression (a check that starts failing, or a file Hardline manages getting edited) pings your phone. Discord and Slack webhooks work the same way.
A bunch of servers
You don't need a control panel. Run hardline fleet from your own laptop. It uses your existing SSH keys and config, audits every server in parallel and gives you one page, worst servers first:
hardline fleet --hosts-file servers.txt --jobs 16 --out fleet.html
The "shared failures" section is the useful bit: fix one thing and roll it out everywhere. To actually apply changes across a fleet, use the Ansible and cloud-init snippets in the docs. Just remember that SSH and firewall changes still need a confirm from a new connection, which a playbook can do.
Block deploys on a bad grade
Hardline exits non-zero when there are findings, and --json gives you the score. That makes it easy to fail a pipeline before a weak server gets traffic:
ssh deploy@new-box 'sudo hardline audit --json' | jq -e '.grade.percent >= 90'
Using GitHub code scanning? hardline audit --sarif uploads straight in, and accepted risks show as dismissed with your reason. More in the docs.
Prometheus and Grafana
Every watch run writes metrics for node_exporter's textfile collector. Hardline opens no ports of its own. The repo ships the rest under contrib/:
prometheus/hardline.rules.yml: four alerts (regression, files modified, grade under 80% for an hour, watch hasn't run in two days), with promtool unit tests.grafana/hardline-dashboard.json: import it, pick your data source, and you get grade per server, history and every failing check.
Handing evidence to an auditor
Auditors want a table, not a vibe. report --audit gives them one: every control with the observed and expected value, its STIG, CCI and CIS references, every accepted risk with who, why and when, and a sign-off line. Print it or save it as a PDF.
sudo hardline --profile cis report --audit --out /root/cis-evidence.html # Ubuntu 24.04
sudo hardline --profile stig report --audit --out /root/stig-evidence.html
A straight answer on what this is: cis covers the CIS Ubuntu 24.04 Level 1 Server recommendations Hardline can check, and stig uses the exact DISA STIG values. It's evidence for your auditor, not a certification, and the report says so.
The file permissions nobody fixes
Phase 8 checks the owner and mode of the files attackers go after first: /etc/shadow and /etc/gshadow (password hashes), /etc/passwd and /etc/group and their backup copies, sshd_config, sudoers, /etc/crontab and the GRUB config. The target values come from the CIS Benchmarks and the DISA STIG, for example 0640 root:shadow for /etc/shadow on Ubuntu/Debian and 0000 on RHEL.
The fix only ever removes permission bits and sets the right owner. It never follows a symlink, and rollback puts back the exact old mode and owner. Run sudo hardline audit --phase 8 to see yours.