Files
ansible_role_proxmox_provision/tasks/fail2ban.yml
Jose d3527c14e4
Some checks failed
ansible-lint / Ansible Lint (push) Failing after 6s
Gitleaks Scan / gitleaks (push) Successful in 4s
Markdown Lint / markdown-lint (push) Successful in 6s
docs 📝: Add Fail2Ban integration tasks to README and directory structure.
Updated the README with instructions on integrating Fail2Ban and modified the directory structure to accommodate new files related to this integration.
2026-02-23 19:36:36 +01:00

291 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 | Ensure firewall directory exists
ansible.builtin.file:
path: /etc/pve/firewall
state: directory
- name: fail2ban | Add Fail2Ban IPSet to cluster firewall
ansible.builtin.blockinfile:
path: "{{ pve_firewall_config }}"
marker: "# {mark} ANSIBLE FAIL2BAN IPSET"
block: |
[IPSET {{ f2b_ipset_name }}]
comment: Fail2Ban dynamic blacklist
create: true
- 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: "{{ pve_firewall_config }}"
marker: "# {mark} ANSIBLE FAIL2BAN RULE"
block: |
IN DROP -source +{{ f2b_ipset_name }}
- 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 }} <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
- 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
#################################################
# 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