Hardline controls

Every Hardline hardening control by phase, from SSH and firewall to kernel and accounts: why it matters, what it checks, how to undo it, plus STIG IDs.

All 67 built-in controls by phase. Every finding in hardline audit links here, and hardline explain <id> prints the same text in your terminal.

SSH · Firewall · Access control · Auditing · TLS · Containers · Kernel · Accounts

Phase 1 · SSH

Key-only SSH login

Passwords can be guessed and are sprayed at every public SSH server all day. Keys can't. Hardline turns off PasswordAuthentication and KbdInteractiveAuthentication and caps login attempts at 3.

Before it does, it counts the keys in every login user's authorized_keys. A user without a key keeps password login through a Match User block, so nobody gets locked out. Add a key, then re-run apply.

  • Key-only SSH: disable keyboard-interactive loginhighssh-kbd-interactive-authenticationbaseline + stig
  • Allow at most 3 SSH authentication attemptslowssh-max-auth-triesbaseline
  • Key-only SSH: disable password loginhighssh-password-authenticationbaseline + stig

No direct root login

Log in as yourself and use sudo, so every root action is tied to a person and goes through the audit log. Hardline sets PermitRootLogin no and checks that root's password is locked (passwd -S root).

  • No direct SSH login as roothighssh-permit-root-loginbaseline + stig
  • Root password is lockedmediumssh-root-password-lockedbaseline + stigUBTU-24-400110

Empty passwords and user environments

PermitEmptyPasswords no stops an account with a blank password from logging in at all. PermitUserEnvironment no stops users from setting variables such as LD_PRELOAD through ~/.ssh/environment, which can get around restrictions on what they're allowed to run.

  • Refuse SSH logins with empty passwordshighssh-permit-empty-passwordsbaseline + stigUBTU-24-300031
  • Ignore user-supplied SSH environment fileshighssh-permit-user-environmentbaseline + stigUBTU-24-300031

Idle session timeout

An SSH session left open on an unattended laptop is a root shell waiting for someone. ClientAliveInterval 600 and ClientAliveCountMax 1 drop a session after 10 minutes with no response.

  • End unresponsive SSH sessions after one keepalivemediumssh-client-alive-count-maxbaseline + stigUBTU-24-600000
  • Drop idle SSH connections after 10 minutesmediumssh-client-alive-intervalbaseline + stigUBTU-24-600010

X11 forwarding

Servers don't need to show windows on your desktop. With X11 forwarding on, a compromised server can reach back into your local X session and log your keystrokes. Hardline sets X11Forwarding no.

  • Disable SSH X11 forwardinghighssh-x11-forwardingbaseline + stigUBTU-24-300022

STIG ciphers, MACs and key exchange

stig profile only. Limits SSH to the FIPS 140-3 approved algorithms the Ubuntu 24.04 STIG requires. Very old clients might not be able to connect afterwards, so baseline keeps OpenSSH's modern defaults.

  • SSH uses FIPS 140-3 approved ciphersmediumssh-stig-ciphersstigUBTU-24-100820
  • SSH uses FIPS 140-3 approved key exchangemediumssh-stig-kexstigUBTU-24-100840
  • SSH uses FIPS 140-3 approved MACsmediumssh-stig-macsstigUBTU-24-100830

Phase 2 · Firewall

Host firewall

Hardline turns on a default-deny inbound firewall, allows every port that's listening right now, and rate-limits SSH per source address. That way, enabling the firewall doesn't take down a running service.

--firewall ufw (the default) uses ufw. --firewall nftables writes a single table inet hardline and never flushes rules that belong to Docker, LXD or anything else. On RHEL, Rocky and AlmaLinux, Hardline installs firewalld if needed, opens SSH and every listening port in the default zone with firewall-offline-cmd, checks the result with --check-config, then starts firewalld. Firewall changes are guarded the same way SSH changes are.

  • A host firewall is activehighfw-activebaseline
  • SSH is rate-limited per source addressmediumfw-ssh-rate-limitedbaseline
  • ufw is enabledmediumfw-ufw-enabledstigUBTU-24-100310
  • ufw is installedmediumfw-ufw-installedstigUBTU-24-100300
  • ufw rate-limits every listening portmediumfw-ufw-limits-listenersstigUBTU-24-600200

Phase 3 · Access control

AppArmor

AppArmor limits what each profiled program can touch, even when it runs as root. Hardline makes sure it's installed, enabled and enforcing. A profile in complain mode only logs what it would block. Hardline reports those profiles but doesn't switch them to enforce, since that can break the program.

  • AppArmor is enabled with policy loadedmediumapparmor-enabledbaseline + stigUBTU-24-100510
  • AppArmor is installedmediumapparmor-installedbaseline + stigUBTU-24-100500
  • No AppArmor profiles in complain modelowapparmor-no-complainbaseline + stig

SELinux

RHEL, Rocky Linux and AlmaLinux 9 only. SELinux confines every service to what its policy allows, even when it runs as root. Hardline checks it is Enforcing now and set to enforcing in /etc/selinux/config, so a reboot doesn't quietly turn it off. Hardline only reports this one: switching it on from disabled needs a full relabel and a reboot, and is best done in a maintenance window.

  • SELinux is enforcing, now and at boothighselinux-enforcingbaseline

Phase 4 · Auditing

Audit logging

auditd records who changed accounts, groups, sudoers and passwords, and every sudo command. Rules go in /etc/audit/rules.d/hardline.rules and are loaded with augenrules. Hardline normalises them the same way auditctl -l prints them, so the comparison is exact.

  • auditd is enabled and runningmediumaudit-auditd-activebaseline + stigUBTU-24-100410
  • auditd is installedmediumaudit-auditd-installedbaseline + stigUBTU-24-100400
  • Audit every use of sudomediumaudit-sudo-execbaseline + stigUBTU-24-900170
  • Audit changes to /etc/groupmediumaudit-watch-groupbaseline + stigUBTU-24-200290
  • Audit changes to /etc/gshadowmediumaudit-watch-gshadowbaseline + stigUBTU-24-200310
  • Audit changes to /etc/security/opasswdmediumaudit-watch-opasswdbaseline + stigUBTU-24-200320
  • Audit changes to /etc/passwdmediumaudit-watch-passwdbaseline + stigUBTU-24-200280
  • Audit changes to /etc/shadowmediumaudit-watch-shadowbaseline + stigUBTU-24-200300
  • Audit changes to /etc/sudoersmediumaudit-watch-sudoersbaseline + stigUBTU-24-900510
  • Audit changes to /etc/sudoers.dmediumaudit-watch-sudoers-dbaseline + stigUBTU-24-900520

Immutable audit rules

stig profile only. -e 2 locks the audit rules until the next reboot, so someone with root access can't quietly switch auditing off. That also applies to you: undoing this control needs a reboot, and Hardline warns before it applies it.

  • Audit rules are immutable until rebootlowaudit-immutablestigUBTU-24-909000

Phase 5 · TLS

TLS on nginx and Apache

Hardline is read-only here. It finds the HTTPS listeners in your nginx or Apache config, checks that only TLS 1.2 and 1.3 are offered, and warns when a certificate expires within 21 days. Web server configs vary too much to edit safely, so each finding says what to change.

  • TLS certificates are not about to expirehightls-cert-expirybaseline + stig
  • Web servers offer only TLS 1.2 and 1.3mediumtls-protocolsbaseline + stig

Phase 6 · Containers

Docker daemon

Hardline merges a few safe defaults into /etc/docker/daemon.json: no traffic between containers on the default bridge, no-new-privileges, live-restore, and capped json-file logs. Your other settings are left alone. It refuses to apply a setting that is also passed as a dockerd flag, because Docker won't start when a setting is in both. Docker isn't restarted for you. Restart it when it's convenient.

Containers running as root are reported but not changed.

  • daemon.json options aren't also passed as dockerd flagshighdocker-flag-conflictsbaseline + stig
  • No traffic between containers on the default bridgemediumdocker-iccbaseline + stig
  • Containers keep running across daemon restartslowdocker-live-restorebaseline + stig
  • Container logs use the json-file driverlowdocker-log-driverbaseline + stig
  • At most 3 rotated log files per containerlowdocker-log-max-filebaseline + stig
  • Container log files are capped at 10 MBlowdocker-log-max-sizebaseline + stig
  • Containers cannot gain new privileges by defaultmediumdocker-no-new-privilegesbaseline + stig
  • Running containers don't run as rootlowdocker-root-containersbaseline + stig

Published container ports

Docker writes its own iptables rules, so -p 5432:5432 is open to the internet even with ufw set to deny everything. Hardline lists every port published on all addresses. Bind them to 127.0.0.1 (-p 127.0.0.1:5432:5432) and put a reverse proxy in front of anything that has to be public.

  • No container ports published on all addressesmediumdocker-published-portsbaseline + stig

Phase 7 · Kernel

Kernel settings

A few sysctl settings that make common local attacks harder: full address space randomization, kernel pointers and the kernel log hidden from normal users, symlink and hardlink protection in shared directories, and SYN cookies against SYN floods. Most are already Ubuntu's defaults. Hardline checks the running value, so a stray file in /etc/sysctl.d that weakens one gets caught.

Fixes go in /etc/sysctl.d/99-zz-hardline.conf, which sorts after Ubuntu's own files so it wins at boot, and are loaded right away with sysctl -p. Rollback puts back the values that were running before.

  • Only root can read the kernel loglowkernel-dmesg-restrictbaseline + stigUBTU-24-600140
  • Kernel pointers are hidden from userslowkernel-kptr-restrictbaseline
  • Full address space layout randomizationmediumkernel-randomize-va-spacebaseline + stigUBTU-24-700310
  • TCP SYN cookies are onmediumkernel-tcp-syncookiesbaseline + stigUBTU-24-600190

Kernel modules

Linux loads drivers for USB storage and a few rarely used network protocols (DCCP, RDS, TIPC) automatically when something asks for them. Old bugs in these protocols have let local users get root, and a server almost never needs them. Hardline writes install <module> /bin/false and blacklist <module> to /etc/modprobe.d/hardline.conf, so the kernel can't load them.

A module that is already loaded stays loaded until the next reboot. Rollback deletes the file.

  • The DCCP network protocol can't be loadedlowkernel-dccpbaseline
  • The RDS network protocol can't be loadedlowkernel-rdsbaseline
  • The TIPC network protocol can't be loadedlowkernel-tipcbaseline
  • USB mass storage can't be loadedmediumkernel-usb-storagebaseline + stigUBTU-24-300039

Phase 8 · Accounts

Accounts

Every account needs a password or a lock. An empty password field in /etc/shadow lets anyone log in at the console. Hardline lists those accounts and leaves the choice to you: sudo passwd <user> or sudo passwd -l <user>.

The stig profile also wants sudo to ask for a password and new accounts to be disabled 35 days after their password expires (INACTIVE=35 in /etc/default/useradd). Cloud images give the default user passwordless sudo, so Hardline only reports that one. Set a password for that user before you remove NOPASSWD, or you lose sudo.

  • New accounts are disabled 35 days after their password expiresmediumaccounts-inactivestigUBTU-24-200260
  • No account has an empty passwordhighaccounts-no-empty-passwordsbaseline + stigUBTU-24-300027
  • sudo asks for a passwordmediumaccounts-sudo-passwordstigUBTU-24-300020

Password rules

Hardline installs libpam-pwquality, which checks new passwords whenever someone runs passwd. SSH keys aren't affected.

The stig profile sets the DISA values: at least 15 characters, with an uppercase letter, a lowercase letter, a digit and a symbol, at least 8 characters different from the old password, no dictionary words, and a minimum age of 1 day and a maximum of 60 in /etc/login.defs. Hardline edits only those lines and leaves the rest of each file alone. The ages apply to accounts created afterwards. pwquality.conf comes with the package, so on a fresh server run apply twice: once to install it, and again to set the values.

  • Passwords expire after 60 daysmediumaccounts-pass-max-daysstigUBTU-24-400310
  • Passwords can be changed at most once a daymediumaccounts-pass-min-daysstigUBTU-24-400300
  • New passwords need a digitmediumaccounts-pw-dcreditstigUBTU-24-400280
  • New passwords can't be dictionary wordsmediumaccounts-pw-dictcheckstigUBTU-24-300014
  • New passwords change at least 8 charactersmediumaccounts-pw-difokstigUBTU-24-400290
  • New passwords need a lowercase lettermediumaccounts-pw-lcreditstigUBTU-24-400270
  • New passwords are at least 15 charactersmediumaccounts-pw-minlenstigUBTU-24-400320
  • New passwords need a special charactermediumaccounts-pw-ocreditstigUBTU-24-400330
  • New passwords need an uppercase lettermediumaccounts-pw-ucreditstigUBTU-24-400260
  • Password quality checks are installedmediumaccounts-pwquality-installedbaseline + stigUBTU-24-100600

Add a control

Each control is a TOML file plus a test, and needs either a STIG reference or a written reason it's baseline-only. Start with CONTRIBUTING.md.