34bceedfd1
Separate drive partitions for /tmp /home /var /var/tmp /var/log /var/log/audit with restrictive mount options
288 lines
10 KiB
Bash
288 lines
10 KiB
Bash
#!/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" |