Compare commits
2 Commits
dev
...
2e570f6808
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e570f6808 | |||
| 1929ad13af |
@@ -27,7 +27,6 @@
|
||||
| Logrotate protection | ✅ | ✅ | ✅ |
|
||||
| Powertop auto-tune | ✅ | ✅ | ✅ |
|
||||
| Utilities | ✅ | ✅ | ✅ |
|
||||
| Fail2Ban Integration | ✅ | ✅ | ✅ |
|
||||
|
||||
## 📂 Directory Structure
|
||||
|
||||
@@ -41,7 +40,6 @@ 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
|
||||
@@ -71,7 +69,6 @@ proxmox_enable_powertop: true
|
||||
## Logrotate
|
||||
proxmox_logrotate_maxsize: "100M"
|
||||
proxmox_logrotate_rotate: 7
|
||||
...
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
@@ -33,12 +33,11 @@ proxmox_apt_languages:
|
||||
- "it"
|
||||
|
||||
# Percentage of total RAM to allocate to log2ram
|
||||
log2ram_ram_percent: 5
|
||||
log2ram_ram_percent: 10
|
||||
|
||||
log2ram_min_size_mb: 128
|
||||
log2ram_max_size_mb: 1024
|
||||
log2ram_host_memtotal_mb: "{{ ansible_memtotal_mb }}"
|
||||
log2ram_version: "1.7.2"
|
||||
|
||||
# Journald RAM usage limit
|
||||
journald_runtime_max_use: "100M"
|
||||
@@ -47,16 +46,3 @@ journald_runtime_max_use: "100M"
|
||||
vm_dirty_ratio: 15
|
||||
vm_dirty_background_ratio: 5
|
||||
vm_swappiness: "{{ proxmox_swapiness }}"
|
||||
|
||||
# Fail2ban settings
|
||||
f2b_bantime: 600 # 10 minutes
|
||||
f2b_findtime: 1200 # 20 minutes
|
||||
f2b_maxretry: 5
|
||||
f2b_recidive_bantime: 3600 # 1 hours
|
||||
f2b_recidive_findtime: 86400 # 24 hours
|
||||
f2b_recidive_maxretry: 3
|
||||
f2b_ipset_name: f2b-blacklist
|
||||
f2b_bantime_increment: true
|
||||
f2b_bantime_factor: 2
|
||||
f2b_bantime_max: 86400
|
||||
f2b_unban_ip: "" # ansible-playbook play.yml -e f2b_unban_ip=192.168.1.55
|
||||
|
||||
@@ -31,21 +31,3 @@
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Reload fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: reloaded
|
||||
enabled: true
|
||||
|
||||
- name: Restart fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
enabled: true
|
||||
|
||||
|
||||
- name: Reload pve firewall
|
||||
ansible.builtin.command: pve-firewall reload
|
||||
when: fw_compile_check.rc == 0
|
||||
changed_when: false
|
||||
|
||||
110
meta/fail2ban.md
110
meta/fail2ban.md
@@ -1,110 +0,0 @@
|
||||
# Fail2Ban Integration with Proxmox Firewall
|
||||
|
||||
This Ansible playbook deploys and configures **Fail2Ban** on a Proxmox VE
|
||||
environment, integrating it with the **Proxmox firewall** for cluster-aware
|
||||
IP banning. It supports both single-node and clustered Proxmox setups.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Detects Proxmox VE installation.
|
||||
- Checks cluster filesystem (`pmxcfs`) and quorum before modifying firewall.
|
||||
- Detects cluster membership via `corosync.conf`.
|
||||
- Installs and configures Fail2Ban with:
|
||||
- SSH protection
|
||||
- Proxmox GUI / AD login protection
|
||||
- Progressive ban escalation (recidive jail)
|
||||
- Deploys a **cluster-aware Fail2Ban action** (`proxmox-fw`) for Proxmox
|
||||
firewall integration.
|
||||
- Ensures safe firewall updates without affecting Corosync ports (5404/5405).
|
||||
- Supports single-node Fail2Ban using `iptables-multiport`.
|
||||
- Enables and starts the Fail2Ban service.
|
||||
- Provides tasks to list or manually unban IPs in the cluster.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Proxmox VE** (any supported version)
|
||||
- **Ansible** ≥ 2.9
|
||||
- Root or sudo access on target nodes
|
||||
- Proxmox firewall enabled for cluster-wide banning (optional, but recommended)
|
||||
|
||||
---
|
||||
|
||||
## Variables
|
||||
|
||||
The playbook uses the following variables (can be defined in a `vars` file or
|
||||
inventory group vars):
|
||||
|
||||
| Variable | Description | Default |
|
||||
|-------------------------|---------------------------------|-----------------|
|
||||
| `f2b_bantime` | Ban per tentativi falliti | `600s` |
|
||||
| `f2b_findtime` | Finestra per contare fallimenti | `1200s` |
|
||||
| `f2b_maxretry` | Tentativi prima del ban | `5` |
|
||||
| `f2b_bantime_increment` | Abilita ban incrementale | `true` |
|
||||
| `f2b_bantime_factor` | Fattore aumento ban | `2` |
|
||||
| `f2b_bantime_max` | Durata massima del ban | `7d` |
|
||||
| `f2b_recidive_bantime` | Ban per recidiva | `3600` |
|
||||
| `f2b_recidive_findtime` | Finestra recidiva | `86400` |
|
||||
| `f2b_recidive_maxretry` | Tentativi recidiva | `3` |
|
||||
| `f2b_ipset_name` | Nome IPSet per IP bannati | `f2b-blacklist` |
|
||||
| `f2b_unban_ip` | IP da sbloccare | `""` |
|
||||
|
||||
> All `clustered` and `pmxcfs_running` checks default to `false` to prevent
|
||||
> errors on non-clustered or single-node setups.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Apply the playbook
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory fail2ban-proxmox.yml
|
||||
```
|
||||
|
||||
### 2. List current banned IPs
|
||||
|
||||
```bash
|
||||
ansible-playbook \
|
||||
-i inventory \
|
||||
fail2ban-proxmox.yml \
|
||||
-e "f2b_ipset_name=fail2ban" \
|
||||
-t list_banned
|
||||
```
|
||||
|
||||
### 3. Unban a specific IP
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory fail2ban-proxmox.yml -e "f2b_unban_ip=1.2.3.4"
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
- Detects Proxmox – ensures the playbook runs only on Proxmox VE hosts.
|
||||
- Cluster safety checks – verifies /etc/pve/.members and corosync.conf
|
||||
for quorum.
|
||||
- Installs Fail2Ban – ensures /etc/fail2ban/jail.local exists and applies
|
||||
configuration.
|
||||
- Cluster-aware action – for clustered nodes, Fail2Ban bans are added to
|
||||
Proxmox firewall and compiled immediately (pve-firewall compile).
|
||||
- Single-node fallback – uses iptables-multiport for nodes not in
|
||||
a cluster.
|
||||
- Corosync protection – prevents firewall rules from dropping cluster
|
||||
communication ports (5404/5405).
|
||||
|
||||
## Notes & Safety
|
||||
|
||||
- The playbook does not copy jail.conf, only manages jail.local.
|
||||
- Firewall rules for clustered nodes are only modified if quorum exists.
|
||||
- pve-firewall compile is called safely (>/dev/null 2>&1 || true)
|
||||
to prevent playbook failure on minor compilation warnings.
|
||||
- Manual unban is supported via f2b_unban_ip variable.
|
||||
- Always verify that the Proxmox firewall is enabled when using
|
||||
cluster-wide bans.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
@@ -1,322 +0,0 @@
|
||||
---
|
||||
# -------------------------------------------------
|
||||
# Deploy Fail2Ban integrated with Proxmox Firewall
|
||||
# -------------------------------------------------
|
||||
|
||||
#################################################
|
||||
# Detect Proxmox
|
||||
#################################################
|
||||
|
||||
- name: fail2ban | Detect Proxmox
|
||||
ansible.builtin.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
|
||||
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
|
||||
|
||||
#################################################
|
||||
# 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 + '/host.fw'
|
||||
}}
|
||||
when: pve_installed.stat.exists | default(false)
|
||||
|
||||
- name: fail2ban | Show firewall config path
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
WARNING: Proxmox firewall config path is: {{ pve_firewall_config}}
|
||||
when: pve_firewall_config is defined
|
||||
|
||||
#################################################
|
||||
# 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 | Debug config contents
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
{{ 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 and
|
||||
(fw_content.content | b64decode)
|
||||
is search('^enable:\s*1$', multiline=True)
|
||||
}}
|
||||
|
||||
- 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 | 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
|
||||
@@ -42,6 +42,3 @@
|
||||
|
||||
- name: Configure ram usage
|
||||
ansible.builtin.import_tasks: ram.yml
|
||||
|
||||
- name: SetUp fail2ban
|
||||
ansible.builtin.import_tasks: fail2ban.yml
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
- name: ram | Download log2ram
|
||||
ansible.builtin.get_url:
|
||||
url: https://github.com/azlux/log2ram/archive/refs/tags/{{ log2ram_version }}.tar.gz
|
||||
url: https://github.com/azlux/log2ram/archive/refs/tags/1.7.2.tar.gz
|
||||
dest: /tmp/log2ram.tar.gz
|
||||
mode: '0644'
|
||||
register: download_archive
|
||||
@@ -47,13 +47,6 @@
|
||||
- name: ram | Logic to extract and install log2ram
|
||||
when: download_archive.changed # noqa: no-handler
|
||||
block:
|
||||
- name: ram | Stop log2ram if present
|
||||
ansible.builtin.systemd:
|
||||
name: log2ram
|
||||
state: stopped
|
||||
become: true
|
||||
failed_when: false
|
||||
|
||||
- name: ram | Extract log2ram
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/log2ram.tar.gz"
|
||||
@@ -63,7 +56,7 @@
|
||||
- name: ram | Install log2ram
|
||||
ansible.builtin.command: bash install.sh
|
||||
args:
|
||||
chdir: "/tmp/log2ram-{{ log2ram_version }}"
|
||||
chdir: "/tmp/log2ram-master"
|
||||
# 'creates' makes the command idempotent by checking for the binary
|
||||
creates: /usr/local/bin/log2ram
|
||||
notify: Restart log2ram
|
||||
|
||||
Reference in New Issue
Block a user