From 95e19506ff7bed93af885ef69fb839f9cd74943a Mon Sep 17 00:00:00 2001
From: Kristian Klausen <kristian@klausen.dk>
Date: Sun, 18 Feb 2024 16:41:26 +0100
Subject: [PATCH] fail2ban: Use a managed firewalld ipset

The firewalld direct interface is deprecated and will be removed in a
future release[1]. Recently IPv4 connectivity inside docker containers
on our runners broke and after some troubleshooting, the issue was
pinpointed to the start of the fail2ban service. We also had issues in
the past where sometimes firewalld had to be restarted after boot before
network connectivity worked in libvirt on our runners.

The issuse may be due to a bug in the way fail2ban use the direct
interface, a bug in firewalld or a combination thereof. Let's just avoid
the direct interface altogether and create a clean separation, with
firewalld handling the blocking and fail2ban maintaining the ipset.

[1] https://firewalld.org/documentation/man-pages/firewalld.direct.html
---
 roles/fail2ban/files/fail2ban.xml                 |  3 +++
 roles/fail2ban/tasks/main.yml                     | 15 +++++++++++++--
 .../templates/firewallcmd-allports.local.j2       |  8 --------
 .../templates/firewallcmd-ipset-allports.conf.j2  |  8 ++++++++
 roles/fail2ban/templates/jail.local.j2            |  2 +-
 5 files changed, 25 insertions(+), 11 deletions(-)
 create mode 100644 roles/fail2ban/files/fail2ban.xml
 delete mode 100644 roles/fail2ban/templates/firewallcmd-allports.local.j2
 create mode 100644 roles/fail2ban/templates/firewallcmd-ipset-allports.conf.j2

diff --git a/roles/fail2ban/files/fail2ban.xml b/roles/fail2ban/files/fail2ban.xml
new file mode 100644
index 000000000..4dbbdb683
--- /dev/null
+++ b/roles/fail2ban/files/fail2ban.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ipset type="hash:net">
+</ipset>
diff --git a/roles/fail2ban/tasks/main.yml b/roles/fail2ban/tasks/main.yml
index dc0642029..856fa42e6 100644
--- a/roles/fail2ban/tasks/main.yml
+++ b/roles/fail2ban/tasks/main.yml
@@ -36,8 +36,8 @@
 
 - name: Install firewallcmd-allports.local
   template:
-    src: "firewallcmd-allports.local.j2"
-    dest: "/etc/fail2ban/action.d/firewallcmd-allports.local"
+    src: "firewallcmd-ipset-allports.conf.j2"
+    dest: "/etc/fail2ban/action.d/firewallcmd-ipset-allports.conf"
     owner: "root"
     group: "root"
     mode: '0644'
@@ -88,6 +88,17 @@
   notify:
     - Reload fail2ban jails
 
+- name: Install fail2ban ipset for firewalld
+  copy: src=fail2ban.xml dest=/etc/firewalld/ipsets/ owner=root group=root mode=0644
+  register: result
+
+- name: Restart firewalld
+  systemd: name=firewalld state=restarted
+  when: result.changed
+
+- name: Add fail2ban ipset to the firewalld drop zone
+  ansible.posix.firewalld: source=ipset:fail2ban zone=drop permanent=true immediate=true state=enabled
+
 - name: Start and enable service
   systemd:
     name: "fail2ban.service"
diff --git a/roles/fail2ban/templates/firewallcmd-allports.local.j2 b/roles/fail2ban/templates/firewallcmd-allports.local.j2
deleted file mode 100644
index 26352a00a..000000000
--- a/roles/fail2ban/templates/firewallcmd-allports.local.j2
+++ /dev/null
@@ -1,8 +0,0 @@
-#
-# {{ansible_managed}}
-#
-
-# creates the requisite chains in firewalld when fail2ban starts instead
-# of creating them on first use (ie, when first IP is banned)
-[Definition]
-actionstart_on_demand = false
diff --git a/roles/fail2ban/templates/firewallcmd-ipset-allports.conf.j2 b/roles/fail2ban/templates/firewallcmd-ipset-allports.conf.j2
new file mode 100644
index 000000000..9cbbd23b9
--- /dev/null
+++ b/roles/fail2ban/templates/firewallcmd-ipset-allports.conf.j2
@@ -0,0 +1,8 @@
+#
+# {{ansible_managed}}
+#
+
+[Definition]
+
+actionban = firewall-cmd --ipset=fail2ban --add-entry=<ip>
+actionunban = firewall-cmd --ipset=fail2ban --remove-entry=<ip>
diff --git a/roles/fail2ban/templates/jail.local.j2 b/roles/fail2ban/templates/jail.local.j2
index f8294d446..5ba5ad898 100644
--- a/roles/fail2ban/templates/jail.local.j2
+++ b/roles/fail2ban/templates/jail.local.j2
@@ -27,7 +27,7 @@ sender = fail2ban@{{ansible_fqdn}}
 #   fail2ban-client set unban <IP>
 #   fail2ban-client set unban --all
 # see `fail2ban-client help` for full list of runtime commands
-banaction = firewallcmd-allports
+banaction = firewallcmd-ipset-allports
 
 # "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
 # will not ban a host which matches an address in this list. Several addresses
-- 
GitLab