From 8708ae55fe8da10a89c96df314caf60b2a248937 Mon Sep 17 00:00:00 2001 From: sarvana7 Date: Mon, 4 May 2026 18:03:47 -0700 Subject: [PATCH] Add Ubuntu VM security update automation script This script automates security updates for Ubuntu VMs, including unattended upgrades, SSH protection, and package integrity checks. --- scripts/ubuntu-vm-security-update.sh | 275 +++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 scripts/ubuntu-vm-security-update.sh diff --git a/scripts/ubuntu-vm-security-update.sh b/scripts/ubuntu-vm-security-update.sh new file mode 100644 index 0000000..ebb9ed6 --- /dev/null +++ b/scripts/ubuntu-vm-security-update.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# vm-security-update.sh +# Robust Ubuntu VM security update + unattended-upgrades setup. +# No Ubuntu Pro / paid features. +# +# Usage: +# sudo bash vm-security-update.sh +# sudo bash vm-security-update.sh --no-reboot +# sudo bash vm-security-update.sh --no-auto-reboot +# sudo bash vm-security-update.sh --dry-run + +LOG_FILE="/var/log/vm-security-update.log" +AUTO_REBOOT="true" +REBOOT_NOW="true" +DRY_RUN="false" +AUTO_REBOOT_TIME="04:00" + +export DEBIAN_FRONTEND=noninteractive +export NEEDRESTART_MODE=a + +log() { + echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" | tee -a "$LOG_FILE" +} + +die() { + log "ERROR: $*" + exit 1 +} + +usage() { + cat </dev/null 2>&1; then + die "apt-get not found." +fi + +# Avoid broken/dangling package states. +log "Repairing any interrupted dpkg/apt state" +run dpkg --configure -a +run apt-get -f install -y + +log "Refreshing package index" +run apt-get update + +# Ubuntu 25.10 known update-check/date/rust-coreutils issue. +# Safe to attempt only when package exists/is installed/available. +if [[ "${VERSION_ID:-}" == "25.10" ]]; then + log "Ubuntu 25.10 detected. Attempting rust-coreutils update-check bug fix if package is available." + if apt-cache show rust-coreutils >/dev/null 2>&1; then + run apt-get install --only-upgrade -y rust-coreutils || run apt-get install -y rust-coreutils + else + log "rust-coreutils package not found in enabled repositories. Skipping." + fi +fi + +log "Installing baseline update/security tools" +run apt-get install -y \ + unattended-upgrades \ + apt-listchanges \ + needrestart \ + debsums \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + +log "Applying all available package updates" +run apt-get update +run apt-get full-upgrade -y + +log "Removing unused packages and cleaning apt cache" +run apt-get autoremove --purge -y +run apt-get autoclean + +log "Configuring unattended-upgrades daily security updates" + +run_bash "cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF' +APT::Periodic::Update-Package-Lists \"1\"; +APT::Periodic::Download-Upgradeable-Packages \"1\"; +APT::Periodic::AutocleanInterval \"7\"; +APT::Periodic::Unattended-Upgrade \"1\"; +EOF" + +# Keep origins simple and Ubuntu-safe. This uses distro variables so it works across Ubuntu versions. +run_bash "cat > /etc/apt/apt.conf.d/51unattended-upgrades-custom <<'EOF' +Unattended-Upgrade::Origins-Pattern { + \"origin=Ubuntu,codename=\${distro_codename},label=Ubuntu\"; + \"origin=Ubuntu,codename=\${distro_codename}-security,label=Ubuntu\"; + \"origin=Ubuntu,codename=\${distro_codename}-updates,label=Ubuntu\"; +}; + +Unattended-Upgrade::Package-Blacklist { +}; + +Unattended-Upgrade::Remove-Unused-Kernel-Packages \"true\"; +Unattended-Upgrade::Remove-New-Unused-Dependencies \"true\"; +Unattended-Upgrade::Remove-Unused-Dependencies \"true\"; + +Unattended-Upgrade::SyslogEnable \"true\"; +Unattended-Upgrade::Verbose \"false\"; + +Unattended-Upgrade::Mail \"\"; +Unattended-Upgrade::MailReport \"on-change\"; + +Unattended-Upgrade::OnlyOnACPower \"false\"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections \"false\"; + +Unattended-Upgrade::Automatic-Reboot \"${AUTO_REBOOT}\"; +Unattended-Upgrade::Automatic-Reboot-WithUsers \"false\"; +Unattended-Upgrade::Automatic-Reboot-Time \"${AUTO_REBOOT_TIME}\"; +EOF" + +log "Enabling unattended-upgrades service if present" +run systemctl enable unattended-upgrades || true +run systemctl restart unattended-upgrades || true + +log "Running unattended-upgrades dry-run validation" +if [[ "$DRY_RUN" == "false" ]]; then + unattended-upgrade --dry-run --debug | tee -a "$LOG_FILE" || die "unattended-upgrade dry-run failed" +else + log "DRY RUN: skipping unattended-upgrade validation execution" +fi + +log "Installing and configuring basic SSH protection" + +run apt-get install -y ufw fail2ban + +# UFW: allow current SSH port safely. +SSH_PORT="$(awk ' + $1 ~ /^[Pp]ort$/ && $2 ~ /^[0-9]+$/ {print $2} +' /etc/ssh/sshd_config | tail -n 1)" + +if [[ -z "$SSH_PORT" ]]; then + SSH_PORT="22" +fi + +log "Detected SSH port: $SSH_PORT" + +run ufw allow "${SSH_PORT}/tcp" comment "SSH" +run ufw --force enable +run ufw status verbose | tee -a "$LOG_FILE" + +# Fail2ban minimal SSH jail. +run_bash "mkdir -p /etc/fail2ban/jail.d" + +run_bash "cat > /etc/fail2ban/jail.d/sshd.local <<'EOF' +[sshd] +enabled = true +port = ssh +filter = sshd +backend = systemd +maxretry = 5 +findtime = 10m +bantime = 1h +EOF" + +run systemctl enable fail2ban +run systemctl restart fail2ban + +log "Checking package integrity database availability" +if [[ "$DRY_RUN" == "false" ]]; then + debsums_init_log="/tmp/debsums-init.log" + if command -v debsums >/dev/null 2>&1; then + log "debsums installed. You can later run: sudo debsums -s" + fi +fi + +log "Final update status" +run apt-get update + +if [[ "$DRY_RUN" == "false" ]]; then + UPGRADABLE_COUNT="$(apt list --upgradable 2>/dev/null | tail -n +2 | wc -l || true)" + log "Packages still upgradable: ${UPGRADABLE_COUNT}" + + if [[ -f /var/run/reboot-required ]]; then + log "Reboot required." + cat /var/run/reboot-required | tee -a "$LOG_FILE" || true + + if [[ "$REBOOT_NOW" == "true" ]]; then + log "Rebooting now..." + reboot + else + log "Reboot skipped because --no-reboot was provided." + log "Run this later: sudo reboot" + fi + else + log "No reboot required." + fi +else + log "DRY RUN complete. No changes were applied." +fi + +log "Completed successfully."