# TODO: configure unattended-upgrades and fail2ban Both packages are installed by the `Install base packages` task in [`tasks/main.yaml`](./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`](../../../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: - https://wiki.debian.org/UnattendedUpgrades - https://github.com/fail2ban/fail2ban --- ## 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: ```conf 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 (or `lineinfile` patch) the `Unattended-Upgrade::Origins-Pattern` list so only `Debian-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 in `defaults/main.yaml`) ### Services / timers to enable The package ships two systemd timers; ensure both are enabled and started: - `apt-daily.timer` - `apt-daily-upgrade.timer` ### Implementation notes - Use a `template` task so the config files are checked into the role (e.g. `templates/20auto-upgrades.j2`, `templates/50unattended-upgrades.j2`) rather than inlined as multi-line `copy` blobs. - Put the `bantime` / `findtime` / `maxretry` / `mail` knobs (or any other tunables) into `defaults/main.yaml` so 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`** — overrides `jail.conf` without conflicting on package upgrades. Minimal content: ```ini [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 = systemd ``` Notes: - `backend = systemd` is the right choice on Debian 13 — auth logs go to journald, not `/var/log/auth.log`, so the default `auto` backend 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.j2` with tunables pulled from `defaults/main.yaml`. - Add a handler that restarts `fail2ban` whenever `jail.local` changes, so updated rules take effect on `ansible-playbook` reruns (not just on first apply). - Verify on a real apply with `fail2ban-client status sshd` and `journalctl -u fail2ban` — first-cut configs often have wrong `logpath` / `backend` combinations and silently never ban anything. --- ## Suggested ordering 1. Land `unattended-upgrades` first — it's a single config file plus two `systemd` enables, low risk, and easy to verify by running `unattended-upgrade --dry-run --debug`. 2. Land `fail2ban` second — same shape (one config file + one service) but the `backend = systemd` detail is the easy thing to get wrong, so it's worth a second look. 3. Once both are merged, delete this file and the now-resolved `TODO:` comments from `tasks/main.yaml`.