--- # ------------------------------------------------- # 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