diff --git a/defaults/main.yml b/defaults/main.yml index 21c8068..c8b9241 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -47,3 +47,15 @@ journald_runtime_max_use: "100M" vm_dirty_ratio: 15 vm_dirty_background_ratio: 5 vm_swappiness: "{{ proxmox_swapiness }}" + +# Fail2ban settings +f2b_bantime: 1800 # 30 minutes +f2b_findtime: 600 +f2b_maxretry: 5 +f2b_recidive_bantime: 86400 # 24 hours +f2b_recidive_findtime: 86400 # 24 hours +f2b_recidive_maxretry: 3 +f2b_ipset_name: f2b-blacklist +f2b_bantime_increment: true +f2b_bantime_factor: 2 +f2b_bantime_max: 86400 diff --git a/handlers/main.yml b/handlers/main.yml index 2ff08d9..b742109 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -31,3 +31,8 @@ - name: Reload systemd ansible.builtin.systemd: daemon_reload: true + +- name: Restart fail2ban + ansible.builtin.systemd: + name: fail2ban + state: restarted \ No newline at end of file diff --git a/tasks/fail2ban.yml b/tasks/fail2ban.yml new file mode 100644 index 0000000..5643e6a --- /dev/null +++ b/tasks/fail2ban.yml @@ -0,0 +1,236 @@ +--- +# ------------------------------------------------- +# Deploy Fail2Ban integrated with Proxmox Firewall +# ------------------------------------------------- + +################################################# +# Detect firewall configuration +################################################# + +- name: fail2ban | Check if cluster firewall config exists + ansible.builtin.stat: + path: /etc/pve/firewall/cluster.fw + register: cluster_fw + +- name: fail2ban | Read cluster firewall config + slurp: + src: /etc/pve/firewall/cluster.fw + register: cluster_fw_content + when: cluster_fw.stat.exists + +- name: fail2ban | Determine if firewall enabled + ansible.builtin.set_fact: + pve_firewall_enabled: >- + {{ + cluster_fw.stat.exists and + (cluster_fw_content.content | b64decode) + is search('enable:\s*1') + }} + +- name: fail2ban | Abort if firewall not enabled + ansible.builtin.fail: + msg: > + Proxmox firewall is not enabled at Datacenter level. + Enable it before deploying Fail2Ban integration. + when: not pve_firewall_enabled + +################################################# +# Validate firewall runtime state +################################################# + +- name: fail2ban | Check firewall runtime status + ansible.builtin.command: pve-firewall status + register: pve_fw_status + changed_when: false + failed_when: false + +- name: fail2ban | Abort if firewall daemon not running + ansible.builtin.fail: + msg: > + Proxmox firewall service is not running. + Run: systemctl enable --now pve-firewall + when: "'Status: enabled' not in pve_fw_status.stdout" + +################################################# +# Detect cluster +################################################# + +- name: fail2ban | Detect Proxmox cluster + ansible.builtin.stat: + path: /etc/pve/corosync.conf + register: cluster_status + +- name: fail2ban | Set cluster fact + ansible.builtin.set_fact: + pve_clustered: "{{ cluster_status.stat.exists }}" + +################################################# +# Corosync safety validation +################################################# + +- name: fail2ban | Validate corosync firewall rules + ansible.builtin.command: pve-firewall compile + register: compiled_fw + changed_when: false + failed_when: false + when: cluster_status.stat.exists + +- name: fail2ban | Fail if corosync ports are being dropped + ansible.builtin.fail: + msg: > + Firewall configuration appears to affect Corosync ports (5404/5405). + Refusing to continue to prevent cluster outage. + when: + - cluster_status.stat.exists + - compiled_fw.stdout is search('5404') + - compiled_fw.stdout is search('DROP') + +################################################# +# Install Fail2Ban +################################################# + +- name: fail2ban | Install fail2ban + ansible.builtin.apt: + name: fail2ban + state: present + update_cache: true + +################################################# +# Create Proxmox firewall IPSet (cluster-wide) +################################################# + +- name: fail2ban | Ensure firewall cluster config exists + ansible.builtin.file: + path: /etc/pve/firewall + state: directory + when: pve_clustered + +- name: fail2ban | Add Fail2Ban IPSet to cluster firewall + ansible.builtin.blockinfile: + path: /etc/pve/firewall/cluster.fw + marker: "# {mark} ANSIBLE FAIL2BAN IPSET" + block: | + [IPSET {{ f2b_ipset_name }}] + create: true + when: pve_clustered + +- name: fail2ban | Add drop rule for Fail2Ban IPSet + ansible.builtin.blockinfile: + path: /etc/pve/firewall/cluster.fw + marker: "# {mark} ANSIBLE FAIL2BAN RULE" + block: | + [RULES] + IN DROP -source +{{ f2b_ipset_name }} + when: pve_clustered + +- name: fail2ban | Extract corosync ring0 address + ansible.builtin.shell: grep ring0_addr /etc/pve/corosync.conf | awk '{print $2}' + register: corosync_ip + changed_when: false + when: cluster_status.stat.exists + +# Then automatically whitelist it in Fail2Ban: +# ignoreip = 127.0.0.1/8 {{ corosync_ip.stdout }} + +################################################# +# Create Fail2Ban Proxmox action +################################################# + +- name: fail2ban | Create Proxmox firewall action + ansible.builtin.copy: + dest: /etc/fail2ban/action.d/proxmox-fw.conf + mode: '0644' + content: | + [Definition] + actionstart = + actionstop = + actioncheck = + actionban = /usr/sbin/pve-firewall ipset add {{ f2b_ipset_name }} + actionunban = /usr/sbin/pve-firewall ipset del {{ f2b_ipset_name }} + +################################################# +# Configure AD-aware jail +################################################# + +- name: fail2ban | Ensure jail.d exists + ansible.builtin.file: + path: /etc/fail2ban/jail.d + state: directory + mode: '0755' + +- name: fail2ban | Configure Fail2Ban jails + ansible.builtin.copy: + dest: /etc/fail2ban/jail.d/proxmox.conf + mode: '0644' + content: | + [DEFAULT] + bantime = {{ f2b_bantime }} + findtime = {{ f2b_findtime }} + maxretry = {{ f2b_maxretry }} + bantime.increment = {{ f2b_bantime_increment }} + bantime.factor = {{ f2b_bantime_factor }} + bantime.max = {{ f2b_bantime_max }} + backend = systemd + banaction = proxmox-fw + ignoreip = 127.0.0.1/8 {{ corosync_ip.stdout | default('') }} 192.168.2.0/24 + + ################################################# + # SSH + ################################################# + [sshd] + enabled = true + journalmatch = _SYSTEMD_UNIT=sshd.service + + ################################################# + # Proxmox GUI + AD authentication + ################################################# + [proxmox-auth] + enabled = true + port = https,8006 + filter = proxmox-auth + logpath = /var/log/pveproxy/access.log + + ################################################# + # Progressive escalation (recidive) + ################################################# + [recidive] + enabled = true + filter = recidive + logpath = /var/log/fail2ban.log + bantime = {{ f2b_recidive_bantime }} + findtime = {{ f2b_recidive_findtime }} + maxretry = {{ f2b_recidive_maxretry }} + banaction = proxmox-fw + notify: + - Restart fail2ban + +################################################# +# AD / Winbind filter +################################################# + +- name: fail2ban | Create AD-aware filter + ansible.builtin.copy: + dest: /etc/fail2ban/filter.d/proxmox-auth.conf + mode: '0644' + content: | + [Definition] + failregex = authentication failure; rhost= + pam_unix\(sshd:auth\): authentication failure;.*rhost= + winbind.*authentication for user.*from failed + ignoreregex = + notify: + - Restart fail2ban + +################################################# +# Enable services +################################################# + +- name: fail2ban | Enable fail2ban + ansible.builtin.systemd: + name: fail2ban + enabled: true + state: started + +- name: fail2ban | Reload Proxmox firewall + ansible.builtin.command: pve-firewall reload + changed_when: false