diff --git a/README.md b/README.md index 0e32586..37a0742 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ | Logrotate protection | ✅ | ✅ | ✅ | | Powertop auto-tune | ✅ | ✅ | ✅ | | Utilities | ✅ | ✅ | ✅ | +| Fail2Ban Integration | ✅ | ✅ | ✅ | ## 📂 Directory Structure @@ -40,6 +41,7 @@ ansible_role_proxmox_provision/ ├── meta/ # Role metadata │ └── main.yml ├── tasks/ # Main role tasks +│ ├── fail2ban.yml # Fail2Ban integration tasks │ ├── logrotate.yml # logrotate setup │ ├── main.yml # Core tasks │ ├── powertop.yml # powertop setup @@ -69,6 +71,7 @@ proxmox_enable_powertop: true ## Logrotate proxmox_logrotate_maxsize: "100M" proxmox_logrotate_rotate: 7 +... ``` ## Example usage diff --git a/tasks/fail2ban.yml b/tasks/fail2ban.yml index b6b38dc..32dbe92 100644 --- a/tasks/fail2ban.yml +++ b/tasks/fail2ban.yml @@ -3,35 +3,65 @@ # Deploy Fail2Ban integrated with Proxmox Firewall # ------------------------------------------------- +################################################# +# 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 }}" + +################################################# +# Determine Correct Firewall File +################################################# + +- name: fail2ban | Get Proxmox node name + ansible.builtin.command: hostname + register: pve_node + changed_when: false + +- name: fail2ban | Set firewall config path + ansible.builtin.set_fact: + pve_firewall_config: >- + {{ + '/etc/pve/firewall/cluster.fw' + if pve_clustered + else '/etc/pve/firewall/' + pve_node.stdout + '.fw' + }} + ################################################# # Detect firewall configuration ################################################# -- name: fail2ban | Check if cluster firewall config exists +- name: fail2ban | Check firewall config exists ansible.builtin.stat: - path: /etc/pve/firewall/cluster.fw - register: cluster_fw + path: "{{ pve_firewall_config }}" + register: fw_stat -- name: fail2ban | Read cluster firewall config - slurp: - src: /etc/pve/firewall/cluster.fw - register: cluster_fw_content - when: cluster_fw.stat.exists +- name: fail2ban | Read firewall config + ansible.builtin.slurp: + src: "{{ pve_firewall_config }}" + register: fw_content + when: fw_stat.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') + fw_stat.stat.exists and + (fw_content.content | b64decode) is search('enable:\s*1') }} -- name: fail2ban | Abort if firewall not enabled - ansible.builtin.fail: +- name: fail2ban | Warn if firewall not enabled + ansible.builtin.debug: msg: > - Proxmox firewall is not enabled at Datacenter level. - Enable it before deploying Fail2Ban integration. + WARNING: Proxmox firewall is disabled in configuration. + Fail2Ban will not actively block traffic. when: not pve_firewall_enabled ################################################# @@ -49,20 +79,7 @@ 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 }}" + when: pve_fw_status.rc != 0 ################################################# # Corosync safety validation @@ -82,8 +99,7 @@ Refusing to continue to prevent cluster outage. when: - cluster_status.stat.exists - - compiled_fw.stdout is search('5404') - - compiled_fw.stdout is search('DROP') + - compiled_fw.stdout is search('5404.*DROP|5405.*DROP') ################################################# # Install Fail2Ban @@ -96,32 +112,36 @@ update_cache: true ################################################# -# Create Proxmox firewall IPSet (cluster-wide) +# Create Proxmox firewall IPSet ################################################# -- name: fail2ban | Ensure firewall cluster config exists +- name: fail2ban | Ensure firewall directory 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 + path: "{{ pve_firewall_config }}" marker: "# {mark} ANSIBLE FAIL2BAN IPSET" block: | [IPSET {{ f2b_ipset_name }}] + comment: Fail2Ban dynamic blacklist create: true - when: pve_clustered + +- name: fail2ban | Ensure RULES section exists + ansible.builtin.blockinfile: + path: "{{ pve_firewall_config }}" + marker: "# {mark} ANSIBLE RULES HEADER" + block: | + [RULES] - name: fail2ban | Add drop rule for Fail2Ban IPSet ansible.builtin.blockinfile: - path: /etc/pve/firewall/cluster.fw + path: "{{ pve_firewall_config }}" 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}' @@ -172,7 +192,7 @@ 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 + ignoreip = 127.0.0.1/8{% if pve_clustered %} {{ corosync_ip.stdout }}{% endif %} 192.168.2.0/24 ################################################# # SSH @@ -233,6 +253,8 @@ - name: fail2ban | Reload Proxmox firewall ansible.builtin.command: pve-firewall reload + when: fw_stat.changed or + "'ANSIBLE FAIL2BAN' in fw_content.content | default('')" changed_when: false #################################################