Comprehensive security update automation: 1. Debian OS (unattended-upgrades) — tightened to security-only: - Removed general Debian updates (prevents feature/breaking changes) - Only Debian-Security origins auto-installed - Email admin on every upgrade via ops@performancewest.net - Auto-reboot at 4 AM if kernel update requires it - needrestart auto-restarts services after library updates 2. Docker CE — major version guard: - Patch updates within pinned major version auto-applied - Major version jumps held + admin alerted for manual review - docker-ce, docker-ce-cli, containerd.io all version-guarded 3. Container base images — daily at 3:30 AM: - Pulls latest base images for all docker-compose services - Compares image digests — only rebuilds if changed - Restarts only affected services (not full stack) - Alerts admin on rebuild failures requiring manual intervention - Covers both prod and dev compose projects 4. k3s — weekly Sunday at 3:45 AM: - Patch updates within current minor auto-applied - Minor/major upgrades alert admin for manual review - Verifies node Ready status after update - Alerts on failures with investigation instructions 5. Admin notifications via SMTP: - [INFO] for successful patches - [WARNING] for available major upgrades needing review - [CRITICAL] for failures requiring immediate intervention - Falls back to syslog if SMTP unavailable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
136 lines
3.8 KiB
YAML
136 lines
3.8 KiB
YAML
---
|
|
- name: Update apt cache
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
|
|
- name: Upgrade all packages
|
|
ansible.builtin.apt:
|
|
upgrade: safe
|
|
|
|
- name: Install common packages
|
|
ansible.builtin.apt:
|
|
name: "{{ common_packages }}"
|
|
state: present
|
|
|
|
- name: Create deploy user
|
|
ansible.builtin.user:
|
|
name: "{{ deploy_user }}"
|
|
shell: /bin/bash
|
|
groups: sudo
|
|
append: true
|
|
create_home: true
|
|
state: present
|
|
|
|
- name: Set authorized key for deploy user
|
|
ansible.posix.authorized_key:
|
|
user: "{{ deploy_user }}"
|
|
key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
|
|
state: present
|
|
|
|
- name: Allow deploy user passwordless sudo
|
|
ansible.builtin.copy:
|
|
content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
|
dest: "/etc/sudoers.d/{{ deploy_user }}"
|
|
mode: "0440"
|
|
validate: "visudo -cf %s"
|
|
|
|
- name: Deploy hardened sshd_config
|
|
ansible.builtin.template:
|
|
src: sshd_config.j2
|
|
dest: /etc/ssh/sshd_config
|
|
owner: root
|
|
group: root
|
|
mode: "0600"
|
|
validate: "sshd -t -f %s"
|
|
notify: Restart sshd
|
|
|
|
- name: Configure UFW defaults - deny incoming
|
|
community.general.ufw:
|
|
direction: incoming
|
|
policy: deny
|
|
|
|
- name: Configure UFW defaults - allow outgoing
|
|
community.general.ufw:
|
|
direction: outgoing
|
|
policy: allow
|
|
|
|
- name: Allow SSH on custom port
|
|
community.general.ufw:
|
|
rule: allow
|
|
port: "{{ ssh_port }}"
|
|
proto: tcp
|
|
comment: "SSH custom port"
|
|
|
|
- name: Enable UFW
|
|
community.general.ufw:
|
|
state: enabled
|
|
|
|
- name: Enable and start chrony
|
|
ansible.builtin.systemd:
|
|
name: chrony
|
|
enabled: true
|
|
state: started
|
|
|
|
- name: Configure unattended-upgrades — security only
|
|
ansible.builtin.copy:
|
|
content: |
|
|
// SECURITY UPDATES ONLY — no feature upgrades
|
|
// General Debian updates excluded to prevent breaking changes
|
|
Unattended-Upgrade::Origins-Pattern {
|
|
"origin=Debian,codename=${distro_codename},label=Debian-Security";
|
|
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
|
|
// Docker CE security patches (from download.docker.com repo)
|
|
"origin=Docker,codename=${distro_codename},label=Docker CE";
|
|
};
|
|
|
|
// Never auto-upgrade these — require manual intervention
|
|
Unattended-Upgrade::Package-Blacklist {
|
|
"docker-ce";
|
|
"docker-ce-cli";
|
|
"containerd.io";
|
|
"k3s";
|
|
};
|
|
|
|
// Auto-remove unused kernel packages and dependencies
|
|
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
|
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
|
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
|
|
// Reboot at 4 AM if a kernel update requires it
|
|
Unattended-Upgrade::Automatic-Reboot "true";
|
|
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
|
|
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
|
|
|
|
// Email admin on upgrades and errors
|
|
Unattended-Upgrade::Mail "{{ smtp_admin_email }}";
|
|
Unattended-Upgrade::MailReport "on-change";
|
|
|
|
// Log to syslog
|
|
Unattended-Upgrade::SyslogEnable "true";
|
|
|
|
// Don't auto-upgrade if dpkg --configure is pending (broken state)
|
|
Unattended-Upgrade::MinimalSteps "true";
|
|
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
|
|
- name: Configure auto-update schedule
|
|
ansible.builtin.copy:
|
|
content: |
|
|
// Run apt update and unattended-upgrade daily
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
|
|
- name: Enable and start unattended-upgrades
|
|
ansible.builtin.systemd:
|
|
name: unattended-upgrades
|
|
enabled: true
|
|
state: started
|