From 058edc345567609d3593265362201de4fd8a05f4 Mon Sep 17 00:00:00 2001 From: Christopher Berger Date: Thu, 28 May 2026 23:27:54 +0000 Subject: [PATCH] Add README.md --- README.md | 560 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 560 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec0f047 --- /dev/null +++ b/README.md @@ -0,0 +1,560 @@ +# 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](#architecture-overview) +- [Prerequisites](#prerequisites) +- [VM Creation in Proxmox](#vm-creation-in-proxmox) +- [SSH Key Setup (Windows)](#ssh-key-setup-windows) +- [Script 1 - partition-layout.sh](#script-1--partition-layoutsh) +- [Script 2 - nist-800-53-harden-v2.sh](#script-2--nist-800-53-harden-v2sh) +- [Script 3 - remediate.sh](#script-3--remediatesh) +- [Deployment Workflow](#deployment-workflow) +- [Post-Template: Cloning](#post-template-cloning) +- [Firewall Management](#firewall-management) +- [NIST 800-53 Control Mapping](#nist-800-53-control-mapping) +- [Wazuh SCA Check Coverage](#wazuh-sca-check-coverage) +- [Known Exceptions](#known-exceptions) +- [Maintenance](#maintenance) +- [Troubleshooting](#troubleshooting) + +--- + +## 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](#ssh-key-setup-windows)) +- 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, 20–64 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: + +```powershell +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: + +```powershell +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`: + +```bash +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: + +```powershell +# 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. + +```bash +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: + +```bash +sudo reboot +# After reboot: +df -h | grep vg_nist +findmnt -t ext4 | grep vg_nist +``` + +**Growing a volume later:** + +```bash +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: + +```bash +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. + +```bash +# 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 35731–35748 (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 10–15 minutes. After the script completes, reboot: + +```bash +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 35619–35639). 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. + +```bash +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 | +| 35604–35607 | dccp, sctp, rds, tipc modules blacklisted | +| 35619–35639 | Firewall fully configured (UFW in remediate.sh, iptables in v2) | +| 35641–35642 | SSH host key permissions set | +| 35643–35661 | Complete SSH hardening rewrite | +| 35664 | Sudo logfile configured | +| 35668 | `su` restricted to sudo group via pam_wheel | +| 35672–35690 | Full PAM stack rewrite (common-auth, common-account, common-password, common-session) | +| 35681/35683 | Password quality: minclass, maxsequence added | +| 35694–35698 | 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 | +| 35725–35726 | `audit=1 audit_backlog_limit=8192` in GRUB | +| 35728–35730 | auditd log retention: keep_logs, space alerts | +| 35731–35748 | 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 ) +``` + +### Setting the GRUB Password (Manual Step) + +This is the one control that cannot be automated because it requires interactive hash generation: + +```bash +# 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 + +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 → **Clone** → **Full 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:** + +```bash +sudo iptables -L -v -n --line-numbers +sudo ip6tables -L -v -n --line-numbers +``` + +**Add a new inbound service (e.g., HTTPS):** + +```bash +# Insert before the LOG rule (check line numbers first) +sudo iptables -I INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT +sudo iptables-save | sudo tee /etc/iptables/rules.v4 +``` + +**Add a new outbound service:** + +```bash +sudo iptables -I OUTPUT -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: + +```bash +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.1–18) | +| 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: + +```bash +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 10–20 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 (35626–35639):** + +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 +``` + +## 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. \ No newline at end of file