First commit

This commit is contained in:
2026-07-20 11:05:07 +02:00
commit b592ab669f
159 changed files with 10294 additions and 0 deletions
@@ -0,0 +1,20 @@
[Definition]
actionstart = iptables -N fail2ban-<name>
iptables -A fail2ban-<name> -j RETURN
iptables -I <chain> -p <protocol> -j fail2ban-<name>
actionstop = iptables -D <chain> -p <protocol> -j fail2ban-<name> || true
iptables -F fail2ban-<name> || true
iptables -X fail2ban-<name> || true
actioncheck = iptables -n -L <chain> | grep -q fail2ban-<name>
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP
[Init]
name = default
protocol = tcp
chain = INPUT
@@ -0,0 +1,30 @@
[Definition]
# Throttle (RATE_LIMIT, Phase 4) plutôt qu'un blocage total : au lieu de
# DROP toutes les connexions de <ip>, ne DROP que celles au-delà du débit
# autorisé. --hashlimit-mode srcip fait déjà le bucketing par IP source
# dans une table hashlimit partagée (f2b-<name>) : pas besoin d'un nom de
# table distinct par IP bannie, une seule table suffit pour toute la jail.
actionstart = iptables -N fail2ban-<name>
iptables -A fail2ban-<name> -j RETURN
iptables -I <chain> -p <protocol> -j fail2ban-<name>
actionstop = iptables -D <chain> -p <protocol> -j fail2ban-<name> || true
iptables -F fail2ban-<name> || true
iptables -X fail2ban-<name> || true
actioncheck = iptables -n -L <chain> | grep -q fail2ban-<name>
# actionunban doit reprendre EXACTEMENT la même spec de règle que
# actionban (seul -I vs -D change) : iptables -D échoue silencieusement
# ("Bad rule") si les deux ne correspondent pas au caractère près.
actionban = iptables -I fail2ban-<name> 1 -s <ip> -m hashlimit --hashlimit-above <hashlimit_rate> --hashlimit-burst <hashlimit_burst> --hashlimit-mode srcip --hashlimit-name f2b-<name> -j DROP
actionunban = iptables -D fail2ban-<name> -s <ip> -m hashlimit --hashlimit-above <hashlimit_rate> --hashlimit-burst <hashlimit_burst> --hashlimit-mode srcip --hashlimit-name f2b-<name> -j DROP
[Init]
name = default
protocol = tcp
chain = INPUT
hashlimit_rate = 10/sec
hashlimit_burst = 20
@@ -0,0 +1,22 @@
[Definition]
actionstart = iptables -N fail2ban-<name>
iptables -A fail2ban-<name> -j RETURN
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
# Correction : <iptables> était un tag invalide dans la version originale
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name> || true
iptables -F fail2ban-<name> || true
iptables -X fail2ban-<name> || true
actioncheck = iptables -n -L <chain> | grep -q fail2ban-<name>
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP
[Init]
name = default
port = ssh
protocol = tcp
chain = INPUT
@@ -0,0 +1,19 @@
[Definition]
actionstart =
actioncheck =
actionstop =
# <name> = nom du jail qui a déclenché l'action
# <port>/<protocol> = valeurs du jail si passées explicitement (voir jail.local),
# sinon les défauts "-" du [Init] ci-dessous (ex. jails
# recidive, qui ne ciblent pas un port/protocole précis)
actionban = /etc/fail2ban/action.d/f2b_mqtt_action_banisher.py \
"{\"action\":\"BAN\",\"ip\":\"<ip>\",\"time\":\"<time>\",\"name\":\"<name>\",\"bantime\":\"<bantime>\",\"port\":\"<port>\",\"protocol\":\"<protocol>\"}"
actionunban = /etc/fail2ban/action.d/f2b_mqtt_action_banisher.py \
"{\"action\":\"UNBAN\",\"ip\":\"<ip>\",\"time\":\"<time>\",\"name\":\"<name>\",\"bantime\":\"<bantime>\",\"port\":\"<port>\",\"protocol\":\"<protocol>\"}"
[Init]
init = Mqtt publisher loaded
port = -
protocol = -
@@ -0,0 +1,79 @@
#!/usr/bin/python3
"""Fail2ban action: publish each ban/unban event to the local MQTT broker."""
import configparser
import datetime
import json
import socket
import sys
import uuid
from pathlib import Path
from typing import Any
import paho.mqtt.publish as publish
CONFIG_FILE = Path('/etc/fail2ban/mqtt.conf')
LOG_FILE = '/var/log/fail2ban.log'
# Timeout global sur toutes les connexions socket (évite de bloquer fail2ban)
socket.setdefaulttimeout(5)
def load_mqtt_config() -> dict[str, Any]:
parser = configparser.ConfigParser()
if not parser.read(CONFIG_FILE):
raise FileNotFoundError(f'{CONFIG_FILE} introuvable — voir mqtt.conf.example')
section = parser['mqtt']
return {
'host': section.get('host', '127.0.0.1'),
'port': section.getint('port', 1883),
'username': section['username'],
'password': section['password'],
}
def log(message: str) -> None:
try:
with open(LOG_FILE, 'a') as f:
f.write('{} [MQTT PUBLISHER] {}\n'.format(datetime.datetime.now(), message))
except OSError as e:
print(f'fail2ban mqtt log error: {e}')
def publish_message(base_topic: str, mqtt_params: dict[str, Any], topic: str, **payload: Any) -> None:
try:
message = json.dumps(payload)
publish.single(base_topic + topic, message, **mqtt_params)
except Exception as e:
log(f'publish_message error: {e}')
def main() -> None:
try:
config = load_mqtt_config()
except Exception as e:
log(f'config error: {e}')
return
node_id = uuid.getnode()
node_type = 'fail2ban'
base_topic = f'{node_type}/{node_id}'
mqtt_params: dict[str, Any] = dict(
qos=0,
retain=False,
hostname=config['host'],
port=config['port'],
keepalive=60,
auth=dict(username=config['username'], password=config['password']),
client_id=f'{node_type}_{node_id}',
)
try:
payload = json.loads(sys.argv[1])
publish_message(base_topic, mqtt_params, '/jail', node=node_id, **payload)
except Exception as e:
log(f'main error: {e}')
if __name__ == '__main__':
main()