Files
NIST-800.53-VM/README.md
T
2026-05-28 23:41:01 +00:00

25 KiB
Raw Blame History

NIST 800-53 Hardened Ubuntu 24.04 - Proxmox VM Template

A hardened Ubuntu 24.04 LTS VM template for Proxmox VE, aligned with NIST 800-53 security controls and validated against CIS Ubuntu 24.04 benchmarks via Wazuh SCA.

The template is built through three scripts run in sequence on a fresh Ubuntu 24.04 Server (minimal) installation. Once complete, the VM is converted to a Proxmox template for rapid, secure clone deployment.


Table of Contents


Architecture Overview

┌──────────────────────────────────────────────────────────┐
│                    Proxmox VM                            │
├──────────────┬───────────────────────────────────────────┤
│  Disk 1      │  Disk 2 (LVM - vg_nist)                   │
│  /dev/sda    │  /dev/sdb                                 │
│  64 GB       │  50 GB                                    │
│              │                                           │
│  sda1: EFI   │  lv_var ............ /var        (10 GB)  │
│  sda2: /boot │  lv_var_log ........ /var/log     (8 GB)  │
│  sda3: LVM   │  lv_var_log_audit .. /var/log/audit (4 GB)│
│    └─ /      │  lv_home ........... /home        (8 GB)  │
│              │  lv_tmp ............ /tmp         (2 GB)  │
│              │  lv_var_tmp ........ /var/tmp     (2 GB)  │
│              │  [~16 GB free for future growth]          │
├──────────────┴───────────────────────────────────────────┤
│  Cloud-Init Drive (auto-injected ISO on /dev/sr0)        │
└──────────────────────────────────────────────────────────┘

Each mount point carries restrictive options:

Mount Point Options
/home nosuid,nodev
/tmp nosuid,nodev,noexec
/var nosuid,nodev
/var/tmp nosuid,nodev,noexec
/var/log nosuid,nodev,noexec
/var/log/audit nosuid,nodev,noexec
/dev/shm nosuid,nodev,noexec

Prerequisites

  • Proxmox VE 8.x or later
  • Ubuntu 24.04 LTS Server ISO (minimal install)
  • An SSH key pair on your admin workstation (see SSH Key Setup)
  • Wazuh agent (optional, for compliance scanning - the scripts pre-configure outbound firewall rules for Wazuh on ports 1514/1515)

VM Creation in Proxmox

Create the VM with these settings before installing Ubuntu:

System tab:

  • Machine: q35
  • BIOS: OVMF (UEFI) with EFI Disk, pre-enroll keys checked (Secure Boot)
  • TPM: v2.0
  • SCSI Controller: VirtIO SCSI single
  • Qemu Agent: checked

Disks:

  • Disk 1 (OS): SCSI, 2064 GB, Discard checked, IO Thread checked
  • Disk 2 (LVM): Added after OS install - SCSI, 50 GB, Discard checked, IO Thread checked

CPU: Type host (exposes AES-NI), 2+ cores

Memory: 2048 MB minimum, Ballooning checked

Network: VirtIO (paravirtualized), Firewall checked

After OS installation, add:

  • The second 50 GB disk (Hardware → Add → Hard Disk)
  • A Cloud-Init drive (Hardware → Add → CloudInit Drive)

Cloud-Init tab:

  • User: your admin username (e.g., chris)
  • SSH public key: paste your id_ed25519.pub
  • IP Config: DHCP or static
  • DNS: your internal servers

SSH Key Setup (Windows)

Generate an Ed25519 key pair from PowerShell on your admin workstation:

ssh-keygen -t ed25519 -C "admin@proxmox"

Accept the default path (C:\Users\YourName\.ssh\id_ed25519) and set a strong passphrase.

Copy the public key:

cat ~/.ssh/id_ed25519.pub

This one public key is used for all VMs cloned from the template. Paste it into both the Cloud-Init tab in Proxmox and (during initial setup) the template VM's ~/.ssh/authorized_keys:

mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA...your-key..." >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Optional - enable the SSH agent so you don't retype the passphrase every session:

# One-time (run as Admin)
Get-Service ssh-agent | Set-Service -StartupType Automatic -PassThru | Start-Service

# Then from regular PowerShell
ssh-add ~/.ssh/id_ed25519

Script 1 - partition-layout.sh

Purpose: Migrates a default single-partition Ubuntu install into a NIST/CIS-compliant LVM layout with six separate mount points on the second disk.

When to run: After Ubuntu install, after attaching the second disk, before the hardening script.

chmod +x partition-layout.sh
sudo ./partition-layout.sh /dev/sdb

What it does:

  1. Installs LVM2 if missing
  2. Creates a GPT partition and LVM volume group (vg_nist) on the second disk
  3. Creates six logical volumes with ext4 filesystems
  4. Migrates existing data from /var, /home, /tmp, etc. using rsync
  5. Writes fstab entries with NIST-compliant mount options
  6. Mounts everything and sets correct permissions (sticky bits, directory modes)
  7. Leaves ~16 GB free in the volume group for future growth

Post-run: Reboot and verify all six volumes mount automatically:

sudo reboot
# After reboot:
df -h | grep vg_nist
findmnt -t ext4 | grep vg_nist

Growing a volume later:

sudo lvextend -L +5G /dev/vg_nist/lv_var_log
sudo resize2fs /dev/vg_nist/lv_var_log

Known issue: The script may fail to mount /var/log/audit because the mount point doesn't exist on the freshly mounted /var/log volume. If this happens:

sudo mkdir -p /var/log/audit
sudo mount /var/log/audit
sudo chmod 700 /var/log/audit

Script 2 - nist-800-53-harden-v2.sh

Purpose: Comprehensive OS hardening covering NIST 800-53 controls and CIS Ubuntu 24.04 benchmarks. Uses iptables (not UFW) for Docker compatibility and clean compliance scans.

When to run: After partition-layout.sh and a successful reboot confirming all mounts.

# Optional: set admin username (defaults to chris)
export ADMIN_USER="chris"

chmod +x nist-800-53-harden-v2.sh
sudo ./nist-800-53-harden-v2.sh

What it does (by NIST control family):

Access Control (AC):

  • Locks root account, removes unnecessary users
  • Configures pam_faillock (5 attempts / 15-min lockout)
  • Sets login warning banners (/etc/issue, /etc/issue.net, /etc/motd)
  • 15-minute idle session timeout (TMOUT with readonly guard)
  • SSH: key-only auth, AllowUsers, PermitRootLogin no, DisableForwarding

Audit and Accountability (AU):

  • Comprehensive auditd rules covering CIS checks 3573135748 (sudoers, user emulation, time changes, network changes, file access, DAC modifications, mounts, deletions, MAC policy, chcon/setfacl/chacl/usermod, kernel modules)
  • Audit log retention: keep_logs, space warnings, admin halt on full
  • Chrony NTP (systemd-timesyncd disabled and masked)
  • Journald: persistent, compressed, rotated, size-limited
  • Rsyslog: file creation mode 0640, remote forwarding placeholder

Identification and Authentication (IA):

  • Password quality: minlen=15, minclass=4, maxsequence=3, maxrepeat=3
  • Password aging: 60-day max, 1-day min, 14-day warning, 30-day inactive lock
  • PAM: faillock, pwhistory (remember=24, enforce_for_root), nullok removed
  • opasswd file created with strict permissions

System and Communications Protection (SC):

  • iptables firewall with default deny on all chains
  • ICMP type-filtered with rate-limited ping (1/sec, burst 4)
  • SSH rate-limited (4 new/min per source IP)
  • Outbound allowed: DNS (53), HTTP (80), HTTPS (443), NTP (123), Wazuh (1514/1515)
  • DOCKER-USER chain pre-created for Docker compatibility
  • Rules persisted via iptables-persistent
  • SSH cryptography: Ed25519/RSA-SHA2 host keys, Curve25519/AES-GCM ciphers, SHA2-ETM MACs
  • /dev/shm hardened with noexec,nosuid,nodev

System and Information Integrity (SI):

  • Kernel hardening: ASLR, ptrace restriction, BPF hardening, core dumps disabled, dmesg restricted
  • AIDE file integrity monitoring with audit tool coverage
  • Unattended security upgrades enabled
  • AppArmor enforced and added to GRUB command line
  • audit=1 audit_backlog_limit=8192 added to GRUB

Configuration Management (CM):

  • Unnecessary services disabled and masked
  • Filesystem modules blacklisted (afs, ceph, cifs, exfat, fat, fscache, fuse, gfs2, nfs, nfsd, smbfs, cramfs, squashfs, etc.)
  • Network protocol modules blacklisted (dccp, sctp, rds, tipc)
  • USB storage disabled
  • Telnet, FTP, rsync, nftables, UFW, apport, snapd removed
  • File permissions hardened, cron/at restricted, su restricted to sudo group
  • Sudo I/O logging enabled

Template Preparation:

  • QEMU guest agent enabled
  • Cloud-init configured for Proxmox (NoCloud/ConfigDrive)
  • Machine-id truncated (regenerated on clone)
  • SSH host keys removed (regenerated on first boot via firstboot-harden.service)
  • initramfs rebuilt, apt cache cleaned, logs truncated

Post-run: AIDE database initialization takes 1015 minutes. After the script completes, reboot:

sudo reboot

Script 3 - remediate.sh

Purpose: Post-scan remediation script that fixes remaining Wazuh SCA failures after the initial hardening. Addresses specific check IDs with targeted fixes.

When to run: After the hardening script and a Wazuh SCA scan reveals remaining failures. Can also be run standalone on an already-hardened system.

Note: This script was written during the initial hardening iteration and uses UFW for firewall rules (sections 3561935639). If you ran nist-800-53-harden-v2.sh (which uses iptables and removes UFW), the UFW sections will be skipped harmlessly since UFW won't be installed. The iptables rules from the v2 hardening script satisfy the same controls.

chmod +x remediate.sh
sudo ./remediate.sh

What it fixes (by Wazuh check ID):

Check ID Description
35509 Filesystem kernel modules blacklisted
35522 nodev added to /var mount
35537 AppArmor enabled in GRUB command line
35545 Apport disabled, masked, and purged
35573 rsync removed
35585 telnet removed
35587 ftp removed
35589 systemd-timesyncd masked (chrony is NTP source)
35600 /etc/cron.allow created
35601 /etc/at.allow created
3560435607 dccp, sctp, rds, tipc modules blacklisted
3561935639 Firewall fully configured (UFW in remediate.sh, iptables in v2)
3564135642 SSH host key permissions set
3564335661 Complete SSH hardening rewrite
35664 Sudo logfile configured
35668 su restricted to sudo group via pam_wheel
3567235690 Full PAM stack rewrite (common-auth, common-account, common-password, common-session)
35681/35683 Password quality: minclass, maxsequence added
3569435698 Password aging and inactive lock on all users
35703 Root umask set in .bashrc and .bash_profile
35708 Journald rotation configured
35719 Rsyslog file creation mode 0640
35722 Log file permissions tightened
3572535726 audit=1 audit_backlog_limit=8192 in GRUB
3572835730 auditd log retention: keep_logs, space alerts
3573135748 Complete CIS-compliant audit ruleset
35752 Audit config file permissions (640)
35755 Audit tool permissions
35760 AIDE integrity rules for audit tools
35770 opasswd/opasswd.old permissions

Deployment Workflow

The full sequence from start to template:

1.  Create VM in Proxmox (settings above)
2.  Install Ubuntu 24.04 Server (minimal)
3.  Attach second 50 GB disk in Proxmox Hardware tab
4.  Add Cloud-Init drive in Proxmox Hardware tab
5.  Boot VM, log in via Proxmox console (noVNC)
6.  Add your SSH public key to ~/.ssh/authorized_keys
7.  Copy scripts to the VM
8.  Run:  sudo ./partition-layout.sh /dev/sdb
9.  Reboot, verify mounts with df -h
10. Run:  sudo ./nist-800-53-harden-v2.sh
11. Reboot, verify SSH access with key auth
12. (Optional) Install Wazuh agent, run SCA scan
13. (Optional) Run:  sudo ./remediate.sh
14. Reboot, re-scan, review results
15. Set GRUB password (manual - see below)
16. Remove Ubuntu install ISO from CD drive
17. Shut down VM
18. Right-click → Convert to Template (or: qm template <VMID>)

Setting the GRUB Password (Manual Step)

This is the one control that cannot be automated because it requires interactive hash generation:

# Generate the hash
sudo grub-mkpasswd-pbkdf2
# Enter and confirm your password, copy the output hash

# Create the GRUB config
sudo tee /etc/grub.d/40_custom > /dev/null <<EOF
#!/bin/sh
exec tail -n +3 \$0
set superusers="grubadmin"
password_pbkdf2 grubadmin <PASTE_YOUR_HASH_HERE>
EOF

sudo chmod 755 /etc/grub.d/40_custom
sudo update-grub

Post-Template: Cloning

To deploy a new VM from the template:

  1. Right-click the template → CloneFull Clone
  2. Set VM ID and name
  3. Go to the clone's Cloud-Init tab and configure:
    • User (or keep the default)
    • SSH public key (or keep the template's key)
    • IP configuration (static or DHCP)
    • DNS servers
  4. Start the VM

On first boot, the firstboot-harden.service automatically:

  • Regenerates SSH host keys
  • Sets correct host key permissions
  • Rebuilds the AIDE integrity database
  • Creates /var/lib/firstboot-done to prevent re-running

The Cloud-Init subsystem handles hostname, user creation, SSH key injection, and network configuration.


Firewall Management

The v2 hardening script uses iptables with persistent rules. Common management tasks:

View current rules:

sudo iptables -L -v -n --line-numbers
sudo ip6tables -L -v -n --line-numbers

Add a new inbound service (e.g., HTTPS):

# Insert before the LOG rule (check line numbers first)
sudo iptables -I INPUT <line> -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4

Add a new outbound service:

sudo iptables -I OUTPUT <line> -p tcp --dport 8080 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4

Docker compatibility: The script pre-creates a DOCKER-USER chain. When Docker is installed, it inserts its own rules but respects the DOCKER-USER chain for custom filtering. To restrict Docker container traffic:

sudo iptables -I DOCKER-USER -s 10.0.0.0/8 -j DROP
sudo iptables-save | sudo tee /etc/iptables/rules.v4

Persistent rules are stored in /etc/iptables/rules.v4 and /etc/iptables/rules.v6, loaded on boot by netfilter-persistent.


NIST 800-53 Control Mapping

Control Title Implementation
AC-2 Account Management Root locked, unnecessary users removed, umask 027
AC-3 Access Enforcement AppArmor, su restricted to sudo group
AC-7 Unsuccessful Logon Attempts pam_faillock: 5 attempts, 15-min lockout
AC-8 System Use Notification /etc/issue, /etc/issue.net, /etc/motd banners
AC-11 Session Lock TMOUT=900 (15-min idle timeout)
AC-17 Remote Access SSH: key-only, AllowUsers, modern crypto
AU-2/3/12 Audit Events, Content, Generation Comprehensive auditd rules (CIS 4.1.3.118)
AU-4 Audit Log Storage keep_logs, space alerts, admin halt
AU-8 Time Stamps Chrony NTP, systemd-timesyncd disabled
CM-2 Baseline Configuration Full system update before hardening
CM-6 Configuration Settings File permissions, sudo logging, cron/at restrictions
CM-7 Least Functionality Services disabled, modules blacklisted, packages purged
IA-5 Authenticator Management Password quality/aging, PAM stack, opasswd
MP-2 Media Access Mount options (noexec, nosuid, nodev)
MP-7 Media Use USB storage module disabled
SC-5 Denial-of-Service Protection SYN cookies, iptables rate limiting
SC-7 Boundary Protection iptables default-deny, connection tracking
SC-8/13 Transmission Confidentiality/Crypto SSH: Curve25519, AES-GCM, SHA2-ETM
SC-28 Protection of Information at Rest Filesystem mount hardening
SI-2 Flaw Remediation Unattended security upgrades
SI-7 Software Integrity AIDE file integrity monitoring
SI-16 Memory Protection ASLR, ptrace, BPF, core dumps disabled

Wazuh SCA Check Coverage

Total checks addressed across all three scripts: 70+

Checks are tracked by their Wazuh SCA IDs (35xxx series) corresponding to the CIS Ubuntu Linux 24.04 LTS Benchmark. See the summary tables at the end of each script for a complete list of check IDs mapped to their fixes.


Known Exceptions

These checks will continue to fail or require environment-specific action. Document them as compensating controls for auditors.

Check ID Description Reason / Compensating Control
35509 fat module not disabled Required for UEFI boot partition (/boot/efi). Built into kernel on 24.04 - blacklisting the loadable module is safe but the check may still flag it.
35540 GRUB bootloader password Requires interactive hash generation. Set manually (see Deployment Workflow).
35589 systemd-timesyncd not enabled Chrony is the NTP source (AU-8). timesyncd is intentionally disabled. Both satisfy the control; the scanner may not detect chrony.
35710 systemd-journal-upload auth Only required if using journal-upload to a remote journal gateway. Most setups use Wazuh or rsyslog instead.
35720 rsyslog remote logging Environment-specific. Placeholder config at /etc/rsyslog.d/99-remote.conf. Wazuh agent serves as a compensating control for centralized log collection.

Maintenance

Quarterly review checklist:

  • Run Wazuh SCA scan and review any new failures
  • Review /var/log/sudo.log and audit logs
  • Verify AIDE integrity: sudo aide --check
  • Check that unattended-upgrades is running: sudo systemctl status unattended-upgrades
  • Review and rotate known-exception documentation
  • Verify NTP sync: chronyc tracking
  • Check LVM free space: sudo vgdisplay vg_nist | grep Free

Updating the template:

  1. Clone the template to a temporary VM
  2. Boot, apply updates (sudo apt update && sudo apt upgrade)
  3. Re-run the hardening script (it's idempotent)
  4. Rebuild AIDE database: sudo aideinit -y -f
  5. Clean up and shut down
  6. Convert to new template, retire the old one

Troubleshooting

Can't SSH in after hardening:

The hardening script sets PasswordAuthentication no and AllowUsers chris (or your configured admin user). Verify your SSH key is in ~/.ssh/authorized_keys and your username matches AllowUsers. Use the Proxmox noVNC console as a fallback - it's a virtual console, not SSH.

TMOUT readonly variable error on login:

The v2 script includes a guard for this. If you see the error on an older install, replace the timeout script:

sudo tee /etc/profile.d/nist-timeout.sh > /dev/null <<'EOF'
if [ -z "${TMOUT:-}" ]; then
    readonly TMOUT=900
    export TMOUT
fi
EOF

AIDE takes a long time:

Initial database creation scans all of /boot, /bin, /sbin, /lib, /usr, and /etc. This takes 1020 minutes on a VM. Subsequent checks are faster. Verify it's running with top -c | grep aide.

Docker bypasses firewall rules:

The v2 script pre-creates the DOCKER-USER chain. Docker respects rules inserted there. To block specific traffic to containers, add rules to DOCKER-USER, not INPUT.

iptables rules lost after reboot:

Verify netfilter-persistent is enabled: sudo systemctl is-enabled netfilter-persistent. Rules should be in /etc/iptables/rules.v4 and rules.v6. To re-save current rules: sudo iptables-save | sudo tee /etc/iptables/rules.v4.

Wazuh still flags firewall checks (3562635639):

If you ran remediate.sh (which uses UFW) and then nist-800-53-harden-v2.sh (which removes UFW), the iptables rules satisfy the same CIS controls. Some Wazuh SCA checks are written for a specific firewall tool. Add Wazuh rule-level exceptions for the ones that don't match your chosen tool (see the Wazuh exception guidance in the conversation that produced these scripts).


File Reference

.
├── README.md                       # This file
├── partition-layout.sh             # Step 1: LVM partition layout
├── nist-800-53-harden-v2.sh        # Step 2: OS hardening (iptables, CIS/Wazuh aligned)
└── remediate.sh                    # Step 3: Post-scan targeted fixes

Post-Clone Identity Reset

When cloning from the template, each new VM must have a unique machine ID and SSH host keys. The firstboot-harden.service handles SSH key regeneration automatically, and cloud-init regenerates the machine ID — but if cloud-init is not configured or the firstboot service didn't run, perform these steps manually after the first boot of each clone.

Regenerate the machine ID:

sudo rm -f /etc/machine-id
sudo systemd-machine-id-setup
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id

Regenerate SSH host keys:

sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart ssh

Without these steps, cloned VMs will share the same machine ID (which breaks DHCP leases, logging, and systemd journal identification) and the same SSH host keys (which triggers host key mismatch warnings on your admin workstation and would allow impersonation between clones).


License

These scripts are provided as-is for educational and operational use. Review and test thoroughly in a non-production environment before deployment. Compliance is a shared responsibility - these scripts address technical controls but do not replace organizational policies, procedures, or risk assessments.