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:
@@ -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"
|
||||||
Reference in New Issue
Block a user