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:

  1. Audit and read it. sudo hardline audit, then hardline explain <id> on anything you're not sure about.
  2. Save a baseline. sudo hardline report --out /root/before.html, so you can show the before and after.
  3. 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).
  4. Accept what you have to keep. If an app really needs something Hardline flags, accept it with a reason and a review date. Then apply leaves it alone, and the grade still tells the truth.
  5. 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.