--- # Security Updates Playbook # Automated security patches and system updates # Usage: ansible-playbook playbooks/security_updates.yml # Usage: ansible-playbook playbooks/security_updates.yml -e "reboot_if_required=true" # Usage: ansible-playbook playbooks/security_updates.yml -e "security_only=true" - name: Apply Security Updates hosts: "{{ host_target | default('debian_clients') }}" gather_facts: yes become: yes vars: security_only: "{{ security_only | default(true) }}" reboot_if_required: "{{ reboot_if_required | default(false) }}" backup_before_update: "{{ backup_before_update | default(true) }}" max_reboot_wait: "{{ max_reboot_wait | default(300) }}" update_docker: "{{ update_docker | default(false) }}" tasks: - name: Check if host is reachable ping: register: ping_result - name: Create update log directory file: path: "/var/log/ansible_updates" state: directory mode: '0755' - name: Get pre-update system info shell: | echo "=== PRE-UPDATE SYSTEM INFO ===" echo "Date: {{ ansible_date_time.iso8601 }}" echo "Host: {{ inventory_hostname }}" echo "Kernel: $(uname -r)" echo "Uptime: $(uptime)" echo "" echo "=== CURRENT PACKAGES ===" dpkg -l | grep -E "(linux-image|linux-headers)" || echo "No kernel packages found" echo "" echo "=== SECURITY UPDATES AVAILABLE ===" apt list --upgradable 2>/dev/null | grep -i security || echo "No security updates available" echo "" echo "=== DISK SPACE ===" df -h / echo "" echo "=== RUNNING SERVICES ===" systemctl list-units --type=service --state=running | head -10 register: pre_update_info changed_when: false - name: Display update plan debug: msg: | 🔒 SECURITY UPDATE PLAN ======================= 🖥️ Host: {{ inventory_hostname }} 📅 Date: {{ ansible_date_time.date }} 🔐 Security Only: {{ security_only }} 🔄 Reboot if Required: {{ reboot_if_required }} 💾 Backup First: {{ backup_before_update }} 🐳 Update Docker: {{ update_docker }} {{ pre_update_info.stdout }} - name: Backup critical configs before update shell: | backup_dir="/var/backups/pre-update-{{ ansible_date_time.epoch }}" mkdir -p "$backup_dir" echo "Creating pre-update backup..." # Backup critical system configs cp -r /etc/ssh "$backup_dir/" 2>/dev/null || echo "SSH config backup failed" cp -r /etc/nginx "$backup_dir/" 2>/dev/null || echo "Nginx config not found" cp -r /etc/systemd "$backup_dir/" 2>/dev/null || echo "Systemd config backup failed" # Backup package list dpkg --get-selections > "$backup_dir/package_list.txt" # Backup Docker configs if they exist if [ -d "/opt/docker" ]; then tar -czf "$backup_dir/docker_configs.tar.gz" /opt/docker 2>/dev/null || echo "Docker config backup failed" fi echo "✅ Backup created at $backup_dir" ls -la "$backup_dir" register: backup_result when: backup_before_update | bool - name: Update package cache apt: update_cache: yes cache_valid_time: 0 register: cache_update - name: Check for available security updates shell: | apt list --upgradable 2>/dev/null | grep -c security || echo "0" register: security_updates_count changed_when: false - name: Check for kernel updates shell: | apt list --upgradable 2>/dev/null | grep -E "(linux-image|linux-headers)" | wc -l register: kernel_updates_count changed_when: false - name: Apply security updates only apt: upgrade: safe autoremove: yes autoclean: yes register: security_update_result when: - security_only | bool - security_updates_count.stdout | int > 0 - name: Apply all updates (if not security only) apt: upgrade: dist autoremove: yes autoclean: yes register: full_update_result when: - not security_only | bool - name: Update Docker (if requested) block: - name: Add Docker GPG key apt_key: url: https://download.docker.com/linux/ubuntu/gpg state: present - name: Add Docker repository apt_repository: repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" state: present - name: Update Docker packages apt: name: - docker-ce - docker-ce-cli - containerd.io state: latest register: docker_update_result - name: Restart Docker service systemd: name: docker state: restarted enabled: yes when: docker_update_result.changed when: update_docker | bool - name: Check if reboot is required stat: path: /var/run/reboot-required register: reboot_required_file - name: Display reboot requirement debug: msg: | 🔄 REBOOT STATUS ================ Reboot Required: {{ reboot_required_file.stat.exists }} Kernel Updates: {{ kernel_updates_count.stdout }} Auto Reboot: {{ reboot_if_required }} - name: Create update report shell: | report_file="/var/log/ansible_updates/update_report_{{ ansible_date_time.epoch }}.txt" echo "🔒 SECURITY UPDATE REPORT - {{ inventory_hostname }}" > "$report_file" echo "=================================================" >> "$report_file" echo "Date: {{ ansible_date_time.iso8601 }}" >> "$report_file" echo "Host: {{ inventory_hostname }}" >> "$report_file" echo "Security Only: {{ security_only }}" >> "$report_file" echo "Reboot Required: {{ reboot_required_file.stat.exists }}" >> "$report_file" echo "" >> "$report_file" echo "=== PRE-UPDATE INFO ===" >> "$report_file" echo "{{ pre_update_info.stdout }}" >> "$report_file" echo "" >> "$report_file" echo "=== UPDATE RESULTS ===" >> "$report_file" {% if security_only %} {% if security_update_result is defined %} echo "Security updates applied: {{ security_update_result.changed }}" >> "$report_file" {% endif %} {% else %} {% if full_update_result is defined %} echo "Full system update applied: {{ full_update_result.changed }}" >> "$report_file" {% endif %} {% endif %} {% if update_docker and docker_update_result is defined %} echo "Docker updated: {{ docker_update_result.changed }}" >> "$report_file" {% endif %} echo "" >> "$report_file" echo "=== POST-UPDATE INFO ===" >> "$report_file" echo "Kernel: $(uname -r)" >> "$report_file" echo "Uptime: $(uptime)" >> "$report_file" echo "Available updates: $(apt list --upgradable 2>/dev/null | wc -l)" >> "$report_file" {% if backup_before_update %} echo "" >> "$report_file" echo "=== BACKUP INFO ===" >> "$report_file" echo "{{ backup_result.stdout }}" >> "$report_file" {% endif %} cat "$report_file" register: update_report - name: Notify about pending reboot debug: msg: | ⚠️ REBOOT REQUIRED =================== Host: {{ inventory_hostname }} Reason: System updates require reboot Kernel updates: {{ kernel_updates_count.stdout }} Manual reboot command: sudo reboot Or run with: -e "reboot_if_required=true" when: - reboot_required_file.stat.exists - not reboot_if_required | bool - name: Reboot system if required and authorized reboot: reboot_timeout: "{{ max_reboot_wait }}" msg: "Rebooting for security updates" pre_reboot_delay: 10 when: - reboot_required_file.stat.exists - reboot_if_required | bool register: reboot_result - name: Wait for system to come back online wait_for_connection: timeout: "{{ max_reboot_wait }}" delay: 30 when: reboot_result is defined and reboot_result.changed - name: Verify services after reboot ansible.builtin.systemd: name: "{{ item }}" loop: - ssh - docker - tailscaled register: service_checks failed_when: false changed_when: false when: reboot_result is defined and reboot_result.changed - name: Final security check shell: | echo "=== FINAL SECURITY STATUS ===" echo "Available security updates: $(apt list --upgradable 2>/dev/null | grep -c security || echo '0')" echo "Reboot required: $([ -f /var/run/reboot-required ] && echo 'Yes' || echo 'No')" echo "Last update: {{ ansible_date_time.iso8601 }}" echo "" echo "=== SYSTEM HARDENING CHECK ===" echo "SSH root login: $(grep PermitRootLogin /etc/ssh/sshd_config | head -1 || echo 'Not configured')" echo "Firewall status: $(ufw status | head -1 || echo 'UFW not available')" echo "Fail2ban status: $(systemctl is-active fail2ban 2>/dev/null || echo 'Not running')" echo "Automatic updates: $(systemctl is-enabled unattended-upgrades 2>/dev/null || echo 'Not configured')" register: final_security_check changed_when: false - name: Display update summary debug: msg: | ✅ SECURITY UPDATE COMPLETE - {{ inventory_hostname }} ============================================= 📅 Update Date: {{ ansible_date_time.date }} 🔐 Security Only: {{ security_only }} 🔄 Reboot Performed: {{ reboot_result.changed if reboot_result is defined else 'No' }} {{ update_report.stdout }} {{ final_security_check.stdout }} {% if post_reboot_verification is defined %} 🔍 POST-REBOOT VERIFICATION: {{ post_reboot_verification.stdout }} {% endif %} 📄 Full report: /var/log/ansible_updates/update_report_{{ ansible_date_time.epoch }}.txt 🔍 Next Steps: - Monitor system stability - Check service functionality - Review security hardening: ansible-playbook playbooks/security_audit.yml ============================================= - name: Send update notification (if configured) debug: msg: | 📧 UPDATE NOTIFICATION Host: {{ inventory_hostname }} Status: Updates applied successfully Reboot: {{ 'Required' if reboot_required_file.stat.exists else 'Not required' }} Security updates: {{ security_updates_count.stdout }} when: send_notifications | default(false) | bool