From 34bceedfd1207bdb6c83af48d24412ecdf643842 Mon Sep 17 00:00:00 2001 From: Christopher Berger Date: Thu, 28 May 2026 23:17:05 +0000 Subject: [PATCH] Add '{partition-layout.sh}' Separate drive partitions for /tmp /home /var /var/tmp /var/log /var/log/audit with restrictive mount options --- partition-layout.sh | 288 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 partition-layout.sh diff --git a/partition-layout.sh b/partition-layout.sh new file mode 100644 index 0000000..0dc5488 --- /dev/null +++ b/partition-layout.sh @@ -0,0 +1,288 @@ +#!/usr/bin/env bash +############################################################################### +# NIST 800-53 / CIS Partition Layout – Ubuntu 24.04 (Proxmox VM Template) +# +# PURPOSE: +# Migrates a default single-partition Ubuntu install into a proper +# LVM-based layout with separate volumes for /tmp, /home, /var, +# /var/tmp, /var/log, and /var/log/audit, each with restrictive +# mount options. +# +# PREREQUISITES: +# - A second virtual disk attached to the VM (e.g., /dev/sdb) +# Add it in Proxmox: Hardware → Add → Hard Disk +# - Run BEFORE the hardening script +# - Run from a root shell (sudo -i) +# +# STRATEGY: +# Uses LVM on the second disk so volumes can be resized later. +# After this script, the original root disk holds only / and /boot. +# +# Usage: +# chmod +x partition-layout.sh +# sudo ./partition-layout.sh /dev/sdb # <-- your second disk +############################################################################### +set -euo pipefail + +LOG="/var/log/partition-layout-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG") 2>&1 + +banner() { printf '\n\e[1;36m>>> %s\e[0m\n' "$1"; } + +if [[ $EUID -ne 0 ]]; then echo "Run as root." >&2; exit 1; fi + +DISK="${1:-}" +if [[ -z "$DISK" ]]; then + echo "Usage: $0 /dev/sdX" + echo " Supply the SECOND disk device (the one you added in Proxmox)." + echo "" + echo "Available disks:" + lsblk -d -o NAME,SIZE,TYPE,MOUNTPOINTS | grep disk + exit 1 +fi + +if [[ ! -b "$DISK" ]]; then + echo "ERROR: $DISK is not a valid block device." >&2 + exit 1 +fi + +# Safety check — abort if the disk has existing partitions with data +if lsblk -n "$DISK" | grep -q 'part'; then + echo "WARNING: $DISK already has partitions:" + lsblk "$DISK" + read -rp "This will DESTROY all data on $DISK. Continue? [y/N] " confirm + [[ "$confirm" =~ ^[Yy]$ ]] || exit 0 +fi + +############################################################################### +# CONFIGURATION – Adjust sizes to your needs +# These are minimums; LVM lets you grow them later with lvextend + resize2fs +############################################################################### +VG_NAME="vg_nist" + +# Size allocations (adjust to your total disk size) +# For a 50 GB second disk, this uses ~38 GB and leaves ~12 GB free in the VG +declare -A LV_SIZES=( + [lv_home]="8G" + [lv_tmp]="2G" + [lv_var]="10G" + [lv_var_tmp]="2G" + [lv_var_log]="8G" + [lv_var_log_audit]="4G" +) + +# Mount options per NIST 800-53 / CIS +declare -A MOUNT_OPTS=( + [/home]="defaults,nosuid,nodev" + [/tmp]="defaults,nosuid,nodev,noexec" + [/var]="defaults,nosuid" + [/var/tmp]="defaults,nosuid,nodev,noexec" + [/var/log]="defaults,nosuid,nodev,noexec" + [/var/log/audit]="defaults,nosuid,nodev,noexec" +) + +# Map LV names to mount points +declare -A LV_MOUNTS=( + [lv_home]="/home" + [lv_tmp]="/tmp" + [lv_var]="/var" + [lv_var_tmp]="/var/tmp" + [lv_var_log]="/var/log" + [lv_var_log_audit]="/var/log/audit" +) + +############################################################################### +# STEP 1: Install LVM tools if missing +############################################################################### +banner "Installing LVM2" +apt-get update -y && apt-get install -y lvm2 + +############################################################################### +# STEP 2: Partition the disk and create the physical volume + volume group +############################################################################### +banner "Preparing $DISK for LVM" + +wipefs -a "$DISK" +parted -s "$DISK" mklabel gpt +parted -s "$DISK" mkpart primary 1MiB 100% +parted -s "$DISK" set 1 lvm on + +# Refresh partition table +partprobe "$DISK" +sleep 2 + +# Identify the partition (sdb1 or vdb1 etc.) +PART="${DISK}1" +# Handle NVMe naming (nvme0n1p1) +if [[ "$DISK" == *nvme* ]] || [[ "$DISK" == *loop* ]]; then + PART="${DISK}p1" +fi + +pvcreate -f "$PART" +vgcreate "$VG_NAME" "$PART" + +echo "Volume group created:" +vgdisplay "$VG_NAME" | grep -E 'VG Name|VG Size|Free' + +############################################################################### +# STEP 3: Create logical volumes +############################################################################### +banner "Creating logical volumes" + +# Order matters: /var must be populated before /var/log and /var/log/audit +ORDERED_LVS=(lv_var lv_var_log lv_var_log_audit lv_home lv_tmp lv_var_tmp) + +for lv in "${ORDERED_LVS[@]}"; do + lvcreate -L "${LV_SIZES[$lv]}" -n "$lv" "$VG_NAME" -y + mkfs.ext4 -L "$lv" "/dev/${VG_NAME}/${lv}" + echo " Created $lv (${LV_SIZES[$lv]})" +done + +echo "" +echo "Remaining free space in VG (available for future growth):" +vgdisplay "$VG_NAME" | grep 'Free' + +############################################################################### +# STEP 4: Migrate data from existing directories to new volumes +# +# We process them in dependency order: /var first, then /var/log, +# then /var/log/audit, so nested mounts copy correctly. +############################################################################### +banner "Migrating data to new volumes" + +# Migration order: deepest-nesting last when copying, first when mounting +MIGRATE_ORDER=(lv_var lv_var_log lv_var_log_audit lv_home lv_tmp lv_var_tmp) + +for lv in "${MIGRATE_ORDER[@]}"; do + mount_point="${LV_MOUNTS[$lv]}" + dev="/dev/${VG_NAME}/${lv}" + temp_mount="/mnt/migrate_${lv}" + + echo "Migrating ${mount_point} ..." + + mkdir -p "$temp_mount" + mount "$dev" "$temp_mount" + + # Copy existing data preserving permissions + if [[ -d "$mount_point" ]]; then + rsync -aAXq "${mount_point}/" "$temp_mount/" 2>/dev/null || true + fi + + umount "$temp_mount" + rmdir "$temp_mount" + + echo " ✓ ${mount_point} data copied to ${dev}" +done + +############################################################################### +# STEP 5: Build fstab entries +############################################################################### +banner "Updating /etc/fstab" + +cp /etc/fstab /etc/fstab.bak.$(date +%s) + +# Remove any existing entries for these mount points (avoid duplicates) +for lv in "${ORDERED_LVS[@]}"; do + mp="${LV_MOUNTS[$lv]}" + # Escape slashes for sed + mp_escaped=$(echo "$mp" | sed 's|/|\\/|g') + sed -i "\| ${mp_escaped} |d" /etc/fstab +done + +# Also remove existing tmpfs /tmp entry if the hardening script added one +sed -i '/tmpfs.*\/tmp/d' /etc/fstab + +# Append new entries +{ + echo "" + echo "# ===== NIST 800-53 Separate Partitions (LVM on $DISK) =====" + for lv in "${ORDERED_LVS[@]}"; do + mp="${LV_MOUNTS[$lv]}" + opts="${MOUNT_OPTS[$mp]}" + echo "/dev/${VG_NAME}/${lv} ${mp} ext4 ${opts} 0 2" + done + echo "" + echo "# /dev/shm hardening" + echo "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" +} >> /etc/fstab + +echo "New fstab:" +cat /etc/fstab + +############################################################################### +# STEP 6: Mount everything and verify +############################################################################### +banner "Mounting all new volumes" + +# Must mount in order: /var before /var/log before /var/log/audit +MOUNT_ORDER=(/var /var/log /var/log/audit /home /tmp /var/tmp) + +for mp in "${MOUNT_ORDER[@]}"; do + mount "$mp" + echo " ✓ Mounted $mp" +done + +# Set sticky bit on /tmp and /var/tmp +chmod 1777 /tmp /var/tmp + +# Ensure /var/log/audit exists with correct ownership +mkdir -p /var/log/audit +chmod 700 /var/log/audit + +# Remount /dev/shm +mount -o remount /dev/shm 2>/dev/null || true + +############################################################################### +# STEP 7: Restart critical services that use /var/log and /tmp +############################################################################### +banner "Restarting services" + +systemctl restart rsyslog 2>/dev/null || true +systemctl restart systemd-journald 2>/dev/null || true +systemctl restart auditd 2>/dev/null || true + +############################################################################### +# VERIFICATION +############################################################################### +banner "Verification" + +echo "" +echo "━━━ Mount Points ━━━" +df -h /home /tmp /var /var/tmp /var/log /var/log/audit 2>/dev/null | \ + awk 'NR==1 || /vg_nist/' +echo "" +echo "━━━ Mount Options ━━━" +for mp in "${MOUNT_ORDER[@]}"; do + opts=$(findmnt -n -o OPTIONS "$mp" 2>/dev/null || echo "NOT MOUNTED") + printf " %-20s %s\n" "$mp" "$opts" +done + +echo "" +echo "━━━ LVM Free Space (for future growth) ━━━" +vgdisplay "$VG_NAME" | grep -E 'Free|VG Size' + +cat <<'DONE' + +╔══════════════════════════════════════════════════════════════════╗ +║ Partition Layout Complete ║ +╠══════════════════════════════════════════════════════════════════╣ +║ ║ +║ Separate mount points created: ║ +║ /home – nosuid, nodev ║ +║ /tmp – nosuid, nodev, noexec ║ +║ /var – nosuid ║ +║ /var/tmp – nosuid, nodev, noexec ║ +║ /var/log – nosuid, nodev, noexec ║ +║ /var/log/audit – nosuid, nodev, noexec ║ +║ /dev/shm – nosuid, nodev, noexec ║ +║ ║ +║ To grow a volume later: ║ +║ lvextend -L +5G /dev/vg_nist/lv_var_log ║ +║ resize2fs /dev/vg_nist/lv_var_log ║ +║ ║ +║ NEXT: Reboot to confirm everything mounts cleanly, ║ +║ then run the hardening script. ║ +╚══════════════════════════════════════════════════════════════════╝ +DONE + +echo "Full log: $LOG" \ No newline at end of file