TODO: configure unattended-upgrades and fail2ban #
Both packages are installed by the Install base packages task in
tasks/main.yaml, but neither is currently configured
or running. The first two TODOs on the original list (devict deploy user
and sshd_config hardening) are intentionally handled out-of-band by the
cloud-init user_data in devserver/terraform/droplet.tf
so they are baked into the droplet at first boot, before Ansible ever
runs. The remaining two belong in this Ansible role.
References:
1. Configure unattended-upgrades #
Goal: automatically apply security patches (and notify/clean up appropriately) without manual intervention.
Files to manage #
-
/etc/apt/apt.conf.d/20auto-upgrades— controls when the timers run. Should contain:APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Unattended-Upgrade "1"; -
/etc/apt/apt.conf.d/50unattended-upgrades— controls what gets upgraded and what side effects happen. On Debian 13 the defaults file is shipped by the package; we should override (orlineinfilepatch) theUnattended-Upgrade::Origins-Patternlist so onlyDebian-Security:*is auto-applied. Other knobs to set:Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";Unattended-Upgrade::Remove-Unused-Dependencies "true";Unattended-Upgrade::Automatic-Reboot "false";(we don't want surprise reboots; handle this explicitly in a maintenance window)Unattended-Upgrade::Mail "root";(or wire to an external address via a variable indefaults/main.yaml)
Services / timers to enable #
The package ships two systemd timers; ensure both are enabled and started:
apt-daily.timerapt-daily-upgrade.timer
Implementation notes #
- Use a
templatetask so the config files are checked into the role (e.g.templates/20auto-upgrades.j2,templates/50unattended-upgrades.j2) rather than inlined as multi-linecopyblobs. - Put the
bantime/findtime/maxretry/mailknobs (or any other tunables) intodefaults/main.yamlso they're easy to override per-environment.
2. Configure fail2ban with sane defaults #
Goal: ban repeat SSH offenders using the shipped sshd filter, and have
the fail2ban service running on boot.
Files to manage #
/etc/fail2ban/jail.local— overridesjail.confwithout conflicting on package upgrades. Minimal content:
Notes:[DEFAULT] # 1 hour ban, triggered by 5 failures within a 10 minute window bantime = 1h findtime = 10m maxretry = 5 [sshd] enabled = true port = ssh filter = sshd backend = systemdbackend = systemdis the right choice on Debian 13 — auth logs go to journald, not/var/log/auth.log, so the defaultautobackend will not work without manual symlinks.- We could add a
[recidive]jail to ban repeat offenders for longer periods. Nice-to-have, not required for the first cut.
Services to enable #
fail2ban.service(enable + started, idempotent)
Implementation notes #
- Same pattern as above: a
templates/jail.local.j2with tunables pulled fromdefaults/main.yaml. - Add a handler that restarts
fail2banwheneverjail.localchanges, so updated rules take effect onansible-playbookreruns (not just on first apply). - Verify on a real apply with
fail2ban-client status sshdandjournalctl -u fail2ban— first-cut configs often have wronglogpath/backendcombinations and silently never ban anything.
Suggested ordering #
- Land
unattended-upgradesfirst — it's a single config file plus twosystemdenables, low risk, and easy to verify by runningunattended-upgrade --dry-run --debug. - Land
fail2bansecond — same shape (one config file + one service) but thebackend = systemddetail is the easy thing to get wrong, so it's worth a second look. - Once both are merged, delete this file and the now-resolved
TODO:comments fromtasks/main.yaml.