Updated the README with instructions on integrating Fail2Ban and modified the directory structure to accommodate new files related to this integration.
291 lines
8.7 KiB
YAML
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 |