Files
ansible_role_proxmox_provision/tasks/fail2ban.yml
Jose 7c304765a4 refactor ♻️: Refactor fail2ban.yml for Proxmox cluster detection and configuration
This commit refactors the fail2ban.yml file to include support for detecting a Proxmox cluster, ensuring that pmxcfs is mounted, installing Fail2Ban, and configuring appropriate jails. This enhances the security and management of the Proxmox environment by automating the setup and monitoring of failed login attempts.
2026-03-01 10:22:41 +01:00

315 lines
9.4 KiB
YAML

---
# -------------------------------------------------
# Deploy Fail2Ban integrated with Proxmox Firewall
# -------------------------------------------------
#################################################
# Detect Proxmox
#################################################
- name: fail2ban | Detect Proxmox
stat:
path: /usr/bin/pveversion
register: pve_installed
#################################################
# Ensure pmxcfs is mounted (cluster filesystem)
#################################################
- name: fail2ban | Check pmxcfs cluster filesystem
ansible.builtin.stat:
path: /etc/pve/.members
register: pmxcfs_running
when: pve_installed.stat.exists | default(false)
- name: fail2ban | Warn if pmxcfs not mounted (no quorum)
ansible.builtin.debug:
msg: >
/etc/pve is not mounted or node has no quorum.
Refusing to modify cluster firewall.
when:
- pve_installed.stat.exists | default(false)
- not pmxcfs_running.stat.exists
#################################################
# Detect cluster membership
#################################################
- name: fail2ban | Detect Proxmox cluster membership
ansible.builtin.stat:
path: /etc/pve/corosync.conf
register: clustered
when: pmxcfs_running.stat.exists | default(false)
- name: fail2ban | Warn if corosync.conf is missing
ansible.builtin.debug:
msg: >
node has no quorum.
Refusing to modify cluster firewall.
when:
- pve_installed.stat.exists | default(false)
- pmxcfs_running.stat.exists | default(false)
- not clustered.stat.exists
#################################################
# Install Fail2Ban
#################################################
- name: fail2ban | Install fail2ban
ansible.builtin.apt:
name: fail2ban
state: present
update_cache: true
#################################################
# Ensure jail.local exists (do NOT copy jail.conf)
#################################################
- name: fail2ban | Ensure jail.local exists
ansible.builtin.file:
path: /etc/fail2ban/jail.local
state: touch
owner: root
group: root
mode: '0644'
#################################################
# Configure Fail2Ban jails
#################################################
- name: fail2ban | Configure Fail2Ban jails
ansible.builtin.blockinfile:
dest: /etc/fail2ban/jail.local
create: true
marker: "# {mark} ANSIBLE MANAGED BLOCK - PROXMOX"
block: |
# jail.conf (default)
# jail.local (override defaults)
[DEFAULT]
bantime = {{ f2b_bantime }}
findtime = {{ f2b_findtime }}
maxretry = {{ f2b_maxretry }}
bantime.increment = {{ f2b_bantime_increment }}
bantime.factor = {{ f2b_bantime_factor }}
bantime.maxtime = {{ f2b_bantime_max }}
backend = systemd
banaction = {% if (clustered.stat.exists | default(false)) %} proxmox-fw{% else %} iptables-multiport{% endif %}
ignoreip = 127.0.0.1/8 192.168.2.0/24
# {% if pmxcfs_running.stat.exists %} {{ corosync_networks | join(' ') }}{% endif %}
#################################################
# SSH
#################################################
[sshd]
enabled = true
#################################################
# Proxmox GUI + AD authentication
#################################################
[proxmox]
enabled = true
port = https,http,8006
filter = proxmox
#################################################
# 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 = {% if (clustered.stat.exists | default(false)) %} proxmox-fw{% else %} iptables-multiport{% endif %}
notify:
- Reload fail2ban
- name: fail2ban | Place Proxmox filter definition
ansible.builtin.copy:
dest: /etc/fail2ban/filter.d/proxmox.conf
content: |
[Definition]
failregex = pvedaemon\[.*authentication failure; rhost=<HOST> user=.* msg=.*
ignoreregex =
journalmatch = _SYSTEMD_UNIT=pvedaemon.service
owner: root
group: root
mode: '0644'
notify:
- Reload fail2ban
#################################################
# Determine Correct Firewall File
#################################################
- name: fail2ban | Get Proxmox node name
ansible.builtin.set_fact:
pve_node: "{{ ansible_hostname }}"
when: not clustered.stat.exists
- name: fail2ban | Set firewall config path
ansible.builtin.set_fact:
pve_firewall_config: >-
{{
'/etc/pve/firewall/cluster.fw'
if clustered.stat.exists
else '/etc/pve/nodes/' + pve_node + '.fw'
}}
when: pve_installed.stat.exists | default(false)
#################################################
# Detect firewall configuration
#################################################
- name: fail2ban | Check firewall config exists
ansible.builtin.stat:
path: "{{ pve_firewall_config }}"
register: fw_stat
when: pve_firewall_config is defined
- name: fail2ban | Read firewall config
ansible.builtin.slurp:
src: "{{ pve_firewall_config }}"
register: fw_content
when: fw_stat.stat.exists | default(false)
- name: fail2ban | Determine if firewall enabled
ansible.builtin.set_fact:
pve_firewall_enabled: >-
{{
(fw_stat.stat.exists | default(false)) and
(
(fw_content.content | default('') | 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
when: pmxcfs_running.stat.exists | default(false)
- name: fail2ban | Abort if firewall daemon not running
ansible.builtin.debug:
msg: >
Proxmox firewall service is not running.
You can run: systemctl enable --now pve-firewall
when:
- pve_fw_status is defined
- pve_fw_status.rc != 0
- fw_stat.stat.exists | default(false)
- pmxcfs_running.stat.exists | default(false)
#################################################
# Corosync safety validation
#################################################
- name: fail2ban | Validate corosync firewall rules
ansible.builtin.command: pve-firewall compile
register: compiled_fw
changed_when: false
failed_when: compiled_fw.rc != 0
when: clustered.stat.exists | default(false)
- name: fail2ban | Fail if corosync ports are being dropped
ansible.builtin.debug:
msg: >
Firewall configuration appears to affect Corosync ports (5404/5405).
Refusing to continue to prevent cluster outage.
when:
- clustered.stat.exists | default(false)
- compiled_fw.stdout is search('5404.*DROP|5405.*DROP')
#################################################
# Deploy cluster-aware Fail2Ban action
#################################################
- name: fail2ban-fw | Deploy proxmox-fw action
ansible.builtin.copy:
dest: /etc/fail2ban/action.d/proxmox-fw.conf
owner: root
group: root
mode: '0644'
content: |
[Definition]
fwfile = {{ pve_firewall_config }}
rule = DROP -source <ip> -log nolog
actionban = \
if [ -f <fwfile> ]; then \
grep -qF "<rule>" <fwfile> || echo "<rule>" >> <fwfile>; \
pve-firewall compile >/dev/null 2>&1 || true; \
fi
actionunban = \
if [ -f <fwfile> ]; then \
sed -i "\|<rule>|d" <fwfile>; \
pve-firewall compile >/dev/null 2>&1 || true; \
fi
actionstart =
actionstop =
when:
- clustered.stat.exists | default(false)
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 is defined and 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