319 lines
11 KiB
YAML
319 lines
11 KiB
YAML
---
|
|
# 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
|