fix(fail2ban): start under ProtectSystem=strict hardening

- log to journald via fail2ban.local (logtarget is a fail2ban.conf option,
  not jail.local) so the server can start without /var/log write access
- create the socket dir via RuntimeDirectory=fail2ban instead of a
  non-creating ReadWritePaths entry
- add ufw write paths so bans persist under the read-only namespace
- manage override.conf unconditionally and restart on change
This commit is contained in:
Clément Désiles
2026-09-05 16:21:49 +02:00
parent ceca06ff2e
commit 120615c38f
5 changed files with 50 additions and 11 deletions
+26
View File
@@ -0,0 +1,26 @@
# Fail2ban
Bans brute-force sources via UFW. Reads logs from the systemd journal (`backend = systemd`), so it works on both Arch and Debian.
## Jails
- `sshd`
- `nginx-http-auth`
## Notes
- Logs to the journal (`logtarget = STDOUT` in `fail2ban.local`) — see `journalctl -u fail2ban`.
- The systemd unit is hardened (`ProtectSystem=strict`); the drop-in lives at `/etc/systemd/system/fail2ban.service.d/override.conf` and is fully managed by the role.
- Firewall/banaction is UFW on every host; jails ban via `ufw prepend reject`.
## Variables
See [defaults/main.yml](defaults/main.yml).
## Verify
```bash
sudo fail2ban-client status
sudo fail2ban-client set sshd banip 192.0.2.1 && sudo ufw status | grep 192.0.2.1
sudo fail2ban-client set sshd unbanip 192.0.2.1
```
+6
View File
@@ -0,0 +1,6 @@
---
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
daemon_reload: true
state: restarted
+12 -9
View File
@@ -13,6 +13,13 @@
mode: "0700" mode: "0700"
recurse: true recurse: true
- name: Deploy fail2ban main config override (log to journald)
ansible.builtin.template:
src: fail2ban.local.j2
dest: /etc/fail2ban/fail2ban.local
mode: "0600"
notify: Restart fail2ban
- name: Install Fail2ban Config - name: Install Fail2ban Config
block: block:
- name: General configuration - name: General configuration
@@ -31,10 +38,6 @@
- name: Service hardening (read-only root rights) - name: Service hardening (read-only root rights)
block: block:
- name: Check if hardening configuration is already applied
ansible.builtin.stat:
path: /etc/systemd/system/fail2ban.service.d/override.conf
register: override_conf
- name: Create configuration directory - name: Create configuration directory
ansible.builtin.file: ansible.builtin.file:
path: /etc/systemd/system/fail2ban.service.d path: /etc/systemd/system/fail2ban.service.d
@@ -42,15 +45,15 @@
owner: root owner: root
group: root group: root
mode: "0700" mode: "0700"
- name: Apply hardening configuration - name: Apply hardening configuration
ansible.builtin.template: ansible.builtin.template:
src: hardened.fail2ban.conf.j2 src: hardened.fail2ban.conf.j2
dest: /etc/systemd/system/fail2ban.service.d/override.conf dest: /etc/systemd/system/fail2ban.service.d/override.conf
when: not override_conf.stat.exists owner: root
- name: Reload systemd group: root
ansible.builtin.systemd: mode: "0644"
daemon_reload: true notify: Restart fail2ban
when: not override_conf.stat.exists
- name: Enable UFW - name: Enable UFW
community.general.ufw: community.general.ufw:
@@ -0,0 +1,2 @@
[DEFAULT]
logtarget = STDOUT
@@ -3,9 +3,11 @@ PrivateDevices=yes
PrivateTmp=yes PrivateTmp=yes
ProtectHome=read-only ProtectHome=read-only
ProtectSystem=strict ProtectSystem=strict
ReadWritePaths=-/var/run/fail2ban RuntimeDirectory=fail2ban
RuntimeDirectoryMode=0750
ReadWritePaths=-/var/lib/fail2ban ReadWritePaths=-/var/lib/fail2ban
ReadWritePaths=-/var/log/fail2ban.log
ReadWritePaths=-/var/spool/postfix/maildrop ReadWritePaths=-/var/spool/postfix/maildrop
ReadWritePaths=-/run/xtables.lock ReadWritePaths=-/run/xtables.lock
ReadWritePaths=-/etc/ufw
ReadWritePaths=-/var/lib/ufw
CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW