Files
ansible_role_proxmox_provision/tasks/fail2ban.yml
Jose 79e14e7120
All checks were successful
ansible-lint / Ansible Lint (push) Successful in 13s
Gitleaks Scan / gitleaks (push) Successful in 5s
Markdown Lint / markdown-lint (push) Successful in 5s
feat : Add IPSET registration and conditional validation for Proxmox firewall configuration
This commit introduces new features to register IPSETs and update firewall reload notifications. It also conditionally validates Proxmox firewall configurations based on changes, enhancing the robustness of the system.
2026-02-24 19:02:24 +01:00

294 lines
8.7 KiB
YAML

---
# -------------------------------------------------
# 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 firewall config exists
ansible.builtin.stat:
path: "{{ pve_firewall_config }}"
register: fw_stat
- 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: >-
{{
fw_stat.stat.exists and
(fw_content.content | b64decode) is search('enable:\s*1')
}}
- name: fail2ban | Warn if firewall not enabled
ansible.builtin.debug:
msg: >
WARNING: Proxmox firewall is disabled in configuration.
Fail2Ban will not actively block traffic.
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: pve_fw_status.rc != 0
#################################################
# 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.*DROP|5405.*DROP')
#################################################
# Install Fail2Ban
#################################################
- name: fail2ban | Install fail2ban
ansible.builtin.apt:
name: fail2ban
state: present
update_cache: true
#################################################
# Create Proxmox firewall IPSet
#################################################
- name: fail2ban | Add Fail2Ban IPSet to firewall
ansible.builtin.blockinfile:
path: "{{ pve_firewall_config }}"
marker: "# {mark} ANSIBLE FAIL2BAN IPSET"
insertbefore: BOF
block: |
[IPSET {{ f2b_ipset_name }}]
comment: Fail2Ban dynamic blacklist
create: false
register: ipset_change
notify: Reload pve firewall
# noqa risky-file-permissions
- name: fail2ban | Add drop rule for Fail2Ban IPSet
ansible.builtin.blockinfile:
path: "{{ pve_firewall_config }}"
marker: "# {mark} ANSIBLE FAIL2BAN RULE"
insertafter: '^\[RULES\]'
block: |
IN DROP -source +{{ f2b_ipset_name }}
create: false
register: rule_change
notify: Reload pve firewall
# noqa risky-file-permissions
- name: fail2ban | Extract corosync ring0 address
ansible.builtin.shell: |
set -o pipefail
grep ring0_addr /etc/pve/corosync.conf | awk '{print $2}'
args:
executable: /bin/bash
register: corosync_ip
changed_when: false
when: cluster_status.stat.exists
- name: fail2ban | Validate Proxmox firewall configuration
ansible.builtin.command: pve-firewall compile
when: ipset_change.changed or rule_change.changed
changed_when: false
failed_when: fw_compile_check.rc != 0
register: fw_compile_check
# 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 }} <ip>
actionunban = /usr/sbin/pve-firewall ipset del {{ f2b_ipset_name }} <ip>
#################################################
# 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{% if pve_clustered %} {{ corosync_ip.stdout }}{% endif %} 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=<HOST>
pam_unix\(sshd:auth\): authentication failure;.*rhost=<HOST>
winbind.*authentication for user.*from <HOST> failed
ignoreregex =
notify:
- Restart fail2ban
#################################################
# Enable services
#################################################
- name: fail2ban | Enable fail2ban
ansible.builtin.systemd:
name: fail2ban
enabled: true
state: started
#################################################
# List banned IPs cluster-wide
#################################################
- name: fail2ban | Get banned IPs from Proxmox IPSet
ansible.builtin.command: pve-firewall ipset list {{ f2b_ipset_name }}
register: banned_ips
changed_when: false
failed_when: false
- name: fail2ban | Show banned IPs
ansible.builtin.debug:
msg: >
Current banned IPs (cluster-wide):
{{ banned_ips.stdout_lines | default([]) }}
#################################################
# Manual unban
#################################################
- name: fail2ban | Unban specific IP
ansible.builtin.command: >
pve-firewall ipset del {{ f2b_ipset_name }} {{ f2b_unban_ip }}
when: f2b_unban_ip | length > 0
register: unban_result
changed_when: "'removed' in unban_result.stdout or unban_result.rc == 0"
failed_when: false
- name: fail2ban | Report unban result
ansible.builtin.debug:
msg: "Unbanned IP {{ f2b_unban_ip }}"
when: f2b_unban_ip | length > 0