Add '{partition-layout.sh}'

Separate drive partitions for /tmp /home /var /var/tmp /var/log /var/log/audit with restrictive mount options
This commit is contained in:
2026-05-28 23:17:05 +00:00
parent 4e3070443d
commit 34bceedfd1
+288
View File
@@ -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"