Compare commits
14 Commits
6f27f0c47c
...
94726374ea
| Author | SHA1 | Date | |
|---|---|---|---|
| 94726374ea | |||
| 13b3a5066d | |||
| 79e14e7120 | |||
| 4107a3a953 | |||
| 8a3f359f46 | |||
| 674f014be3 | |||
| 432ec97292 | |||
| 80b3b82bf6 | |||
| a120b1042b | |||
| e26b3f01f8 | |||
| 8d40abc15b | |||
| d3527c14e4 | |||
| 3afa853d09 | |||
| 4fa35ca62d |
@@ -27,6 +27,7 @@
|
|||||||
| Logrotate protection | ✅ | ✅ | ✅ |
|
| Logrotate protection | ✅ | ✅ | ✅ |
|
||||||
| Powertop auto-tune | ✅ | ✅ | ✅ |
|
| Powertop auto-tune | ✅ | ✅ | ✅ |
|
||||||
| Utilities | ✅ | ✅ | ✅ |
|
| Utilities | ✅ | ✅ | ✅ |
|
||||||
|
| Fail2Ban Integration | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
## 📂 Directory Structure
|
## 📂 Directory Structure
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ ansible_role_proxmox_provision/
|
|||||||
├── meta/ # Role metadata
|
├── meta/ # Role metadata
|
||||||
│ └── main.yml
|
│ └── main.yml
|
||||||
├── tasks/ # Main role tasks
|
├── tasks/ # Main role tasks
|
||||||
|
│ ├── fail2ban.yml # Fail2Ban integration tasks
|
||||||
│ ├── logrotate.yml # logrotate setup
|
│ ├── logrotate.yml # logrotate setup
|
||||||
│ ├── main.yml # Core tasks
|
│ ├── main.yml # Core tasks
|
||||||
│ ├── powertop.yml # powertop setup
|
│ ├── powertop.yml # powertop setup
|
||||||
@@ -69,6 +71,7 @@ proxmox_enable_powertop: true
|
|||||||
## Logrotate
|
## Logrotate
|
||||||
proxmox_logrotate_maxsize: "100M"
|
proxmox_logrotate_maxsize: "100M"
|
||||||
proxmox_logrotate_rotate: 7
|
proxmox_logrotate_rotate: 7
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example usage
|
## Example usage
|
||||||
|
|||||||
@@ -47,3 +47,16 @@ journald_runtime_max_use: "100M"
|
|||||||
vm_dirty_ratio: 15
|
vm_dirty_ratio: 15
|
||||||
vm_dirty_background_ratio: 5
|
vm_dirty_background_ratio: 5
|
||||||
vm_swappiness: "{{ proxmox_swapiness }}"
|
vm_swappiness: "{{ proxmox_swapiness }}"
|
||||||
|
|
||||||
|
# Fail2ban settings
|
||||||
|
f2b_bantime: 1800 # 30 minutes
|
||||||
|
f2b_findtime: 600
|
||||||
|
f2b_maxretry: 5
|
||||||
|
f2b_recidive_bantime: 86400 # 24 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,3 +31,13 @@
|
|||||||
- name: Reload systemd
|
- name: Reload systemd
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: Restart fail2ban
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: fail2ban
|
||||||
|
state: reloaded
|
||||||
|
|
||||||
|
- name: Reload pve firewall
|
||||||
|
ansible.builtin.command: pve-firewall reload
|
||||||
|
when: fw_compile_check.rc == 0
|
||||||
|
changed_when: false
|
||||||
|
|||||||
310
tasks/fail2ban.yml
Normal file
310
tasks/fail2ban.yml
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
---
|
||||||
|
# -------------------------------------------------
|
||||||
|
# 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.set_fact:
|
||||||
|
pve_node: "{{ ansible_hostname }}"
|
||||||
|
|
||||||
|
- 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: fw_compile_check.rc != 0
|
||||||
|
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 all corosync ring addresses
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -o pipefail
|
||||||
|
awk '/ring[0-9]+_addr/ {print $2}' /etc/pve/corosync.conf
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
register: corosync_ips
|
||||||
|
changed_when: false
|
||||||
|
when: pve_clustered
|
||||||
|
|
||||||
|
- name: fail2ban | Determine CIDR for each corosync IP
|
||||||
|
ansible.builtin.command: ip route get {{ item }}
|
||||||
|
register: corosync_routes
|
||||||
|
changed_when: false
|
||||||
|
loop: "{{ corosync_ips.stdout_lines }}"
|
||||||
|
when: pve_clustered
|
||||||
|
|
||||||
|
- name: fail2ban | Extract network CIDRs
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
corosync_networks: >-
|
||||||
|
{{
|
||||||
|
corosync_routes.results
|
||||||
|
| map(attribute='stdout')
|
||||||
|
| map('regex_search', 'src ([0-9.]+)/([0-9]+)', '\\1/\\2')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
when: pve_clustered
|
||||||
|
|
||||||
|
- 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_networks | join(' ') }}{% 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
|
||||||
Reference in New Issue
Block a user