Files
NIST-800.53-VM/partition-layout.sh
T
chris 34bceedfd1 Add '{partition-layout.sh}'
Separate drive partitions for /tmp /home /var /var/tmp /var/log /var/log/audit with restrictive mount options
2026-05-28 23:17:05 +00:00

288 lines
10 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"