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,48 @@
# GABARIT réutilisable pour protéger une NOUVELLE appli contre le
# brute-force applicatif — n'importe quelle appli sur ce VPS, pas
# seulement ce projet (trouvé utile après un résidu de jail appartenant à
# une autre appli Django sur ce même serveur partagé, cf. CONTEXT.md).
# Ce fichier n'est PAS déployé tel quel (extension .template, ignorée par
# la boucle de copie générique d'install.sh::deploy_fail2ban) : c'est un
# modèle à copier et adapter. Exemple concret déjà en place :
# emitter-admin-auth.conf + emitter/banevents/signals.py (Django +
# django-axes).
#
# Mode d'emploi :
# 1. Faire journaliser à l'appli une ligne EXACTEMENT au format attendu
# par filter.d/app-auth.conf (générique, partagé par toutes les
# jails app-auth-*, rien à écrire côté filtre) :
# Verrouillage <NOM_APPLI> après échecs répétés depuis <IP> (...)
# <NOM_APPLI> = un seul mot (\S+ côté filtre), ex. "émetteur",
# "webmail". L'appli décide QUAND verrouiller (son propre seuil
# applicatif, cf. AXES_FAILURE_LIMIT pour l'exemple django-axes) —
# fail2ban ne fait que réagir à cette décision déjà prise (d'où
# maxretry=1 ci-dessous : chaque ligne EST déjà la décision, pas la
# peine de recompter un 2e seuil derrière).
# IMPORTANT — timestamp en tête de ligne : marquer explicitement le
# fuseau (ex. suffixe "Z" ISO 8601, UTC), voir le commentaire dans
# filter.d/app-auth.conf pour le pourquoi (piège vécu avec Django,
# TIME_ZONE='UTC' + time.tzset() : sans marqueur, fail2ban ignore
# silencieusement les lignes fraîches juste après son propre
# redémarrage si le fuseau système du VPS diffère d'UTC).
# 2. Copier ce fichier vers jail.d/<nom-appli>-auth.conf, remplacer
# __APP_NAME__ (nom de la jail, unique) et __APP_AUTH_LOGPATH__
# (chemin réel du fichier de log).
# 3. Si ce chemin dépend du VPS (répertoire de déploiement différent
# d'un serveur à l'autre — le cas courant) : NE PAS le laisser dans
# la boucle de copie générique d'install.sh::deploy_fail2ban (même
# piège que coturn.conf/emitter-admin-auth.conf, voir CONTEXT.md).
# Ajouter plutôt une fonction deploy_<nom>_jail dédiée (sed sur le
# placeholder), sur le modèle de deploy_emitter_admin_auth_jail.
# 4. S'assurer que le fichier de log existe déjà (touch) avant le
# premier démarrage de fail2ban après déploiement — un logpath
# inexistant fait planter fail2ban au chargement (incident coturn,
# voir ROADMAP.md Phase 1.b).
[__APP_NAME__-auth]
enabled = true
backend = auto
filter = app-auth
logpath = __APP_AUTH_LOGPATH__
maxretry = 1
findtime = 1h
bantime = 24h
+19
View File
@@ -0,0 +1,19 @@
# logpath : chemin réel du log coturn sur CE VPS, substitué par install.sh
# (deploy_fail2ban) depuis "log-file=" dans /etc/turnserver.conf s'il existe
# — jamais une valeur codée en dur partagée entre noeuds. Un chemin diffère
# réellement d'un VPS à l'autre selon la config coturn locale (vécu :
# /var/log/coturn/turnserver.log sur l'un, /var/log/turnserver.log sur
# l'autre, même paquet Debian). Si coturn n'est pas installé sur ce noeud,
# install.sh désactive cette jail plutôt que de laisser un chemin invalide
# faire planter fail2ban au démarrage ("Have not found any log file").
[coturn]
enabled = true
backend = auto
filter = coturn
logpath = __COTURN_LOGPATH__
port = 3478
protocol = tcp
banaction = f2b-iptables-allports
maxretry = 5
findtime = 5m
bantime = 24h
+18
View File
@@ -0,0 +1,18 @@
# Désactivée (2026-07-17) : ne protège rien de réel. Le filtre référencé
# (filter.d/django-auth.conf) n'existe pas dans ce dépôt — sur le VPS où
# ce jail.d a été trouvé déployé, un fichier du même nom existe bien, mais
# c'est un résidu appartenant à une autre appli Django hébergée sur ce VPS
# partagé (pas ce projet), et logpath (/var/log/django/*.log) pointe vers
# un fichier vide que rien n'alimente. Notre propre émetteur Django
# (emitter/) n'a aucune config LOGGING et n'écrit jamais là — l'ancien
# commentaire "bloqué sur Phase 2" n'a plus de sens, Phase 2 est terminée
# depuis longtemps et ne visait de toute façon pas ce fichier. À réactiver
# seulement avec un vrai filtre + logpath réels si un jour ce projet doit
# protéger la connexion admin de l'émetteur lui-même.
[django-auth]
enabled = false
backend = auto
port = http,https
logpath = /var/log/django/*.log
maxretry = 3
bantime = 1h
@@ -0,0 +1,21 @@
# logpath : chemin réel du log axes sur CE VPS (emitter/logs/admin-auth.log
# dans le checkout), substitué par install.sh (deploy_fail2ban) — jamais
# une valeur codée en dur, le checkout vit sous un $HOME différent par VPS
# (même piège que coturn.conf/master-sync : voir CONTEXT.md).
#
# maxretry=1 : chaque ligne journalisée EST déjà la décision de
# verrouillage d'axes (AXES_FAILURE_LIMIT, cf. config/settings/base.py) —
# fail2ban n'a pas à recompter un second seuil, il bannit dès la première
# occurrence.
#
# filter = app-auth (générique, pas emitter-admin-auth) : premier
# consommateur du gabarit réutilisable par toute appli du VPS, voir
# filter.d/app-auth.conf et jail.d/app-auth.conf.template.
[emitter-admin-auth]
enabled = true
backend = auto
filter = app-auth
logpath = __EMITTER_ADMIN_AUTH_LOGPATH__
maxretry = 1
findtime = 1h
bantime = 24h
@@ -0,0 +1,15 @@
# Jail dédiée à la commande ESCALATE (Phase 4) — jamais déclenchée par un
# log, uniquement par injection manuelle (fail2ban-client set banip)
# depuis master_client.py sur réception d'un ESCALATE (auto-publié par
# master_listen.py::correlation_rule_escalate quand une IP est bannie
# indépendamment sur plusieurs noeuds distincts, cf. ROADMAP.md).
# banaction = f2b-iptables-allports, bantime = -1 (permanent) : contraire
# à master-sync (24h), une IP qui frappe plusieurs noeuds indépendamment
# n'a pas droit à un simple ban temporaire.
[master-escalate]
enabled = true
filter = master-sync
logpath = /var/log/fail2ban.log
banaction = f2b-iptables-allports
bantime = -1
maxretry = 999999
@@ -0,0 +1,13 @@
# Jail dédiée à la commande RATE_LIMIT (Phase 4) — jamais déclenchée par
# un log, uniquement par injection manuelle (fail2ban-client set banip)
# depuis master_client.py sur réception de RATE_LIMIT (déclenchée à la
# main via manage.py publish_command, cf. master-sync.conf pour le même
# schéma). banaction = f2b-iptables-hashlimit : throttle (10 req/s, burst
# 20) au lieu d'un blocage total — cas limite, faux positifs probables.
[master-ratelimit]
enabled = true
filter = master-sync
logpath = /var/log/fail2ban.log
banaction = f2b-iptables-hashlimit
bantime = 1h
maxretry = 999999
+12
View File
@@ -0,0 +1,12 @@
# Jail dédiée à la propagation SYNC_BAN reçue du master (Phase 3/4) —
# jamais déclenchée par un log, seulement par injection manuelle
# (fail2ban-client set master-sync banip <ip>) depuis master_client.py.
# banaction = f2b-iptables-allports : un SYNC_BAN bloque l'IP sur tous les
# ports, cohérent avec la menace globale qu'il signale (vue ailleurs).
[master-sync]
enabled = true
filter = master-sync
logpath = /var/log/fail2ban.log
banaction = f2b-iptables-allports
bantime = 1d
maxretry = 999999
+13
View File
@@ -0,0 +1,13 @@
[mosquitto]
enabled = true
backend = auto
filter = mosquitto
logpath = /var/log/mosquitto/mosquitto.log
port = 8883,8884
protocol = tcp
banaction = f2b-iptables-multiport
# maxretry élevé car les clients MQTT se reconnectent légitimement
# Réduire à 5 si tu utilises l'approche B (plugin avec IP sur échec auth)
maxretry = 15
findtime = 1m
bantime = 1h
+76
View File
@@ -0,0 +1,76 @@
[nginx-http-auth]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*error.log
[nginx-req-limit]
enabled = true
backend = auto
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/*error.log
findtime = 30s
maxretry = 5
bantime = 10m
[nginx-botsearch]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*error.log
[nginx-noscript]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 3
findtime = 5m
bantime = 15m
[nginx-unauthorized]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 3
findtime = 5m
bantime = 15m
[nginx-badbots]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 1
findtime = 1h
bantime = 24h
[nginx-nohome]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 5
findtime = 5m
bantime = 15m
[nginx-noproxy]
enabled = true
backend = auto
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 3
findtime = 5m
bantime = 15m
# Activer si trop de 404/403 en dehors des autres filtres
[nginx-4xx]
enabled = false
backend = auto
port = http,https
logpath = /var/log/nginx/*.log
maxretry = 10
findtime = 5m
bantime = 15m
+14
View File
@@ -0,0 +1,14 @@
[openvpn]
enabled = true
backend = auto
filter = openvpn
# Adapter le chemin selon la config OpenVPN (log ou log-append)
logpath = /var/log/openvpn/openvpn.log
/var/log/openvpn.log
port = 41194
protocol = udp
# Bannir tous les ports (pas seulement VPN) car la menace est globale
banaction = f2b-iptables-allports
maxretry = 3
findtime = 5m
bantime = 24h
+25
View File
@@ -0,0 +1,25 @@
# Récidive — escalade automatique des bans
# Les jails recidive-* sont exclus de la détection croisée par le filtre
# (ignoreregex = \[recidive.*\])
# Niveau 1 : 3 bans en 24h → 24h de ban
[recidive]
enabled = true
filter = recidive-filter
logpath = /var/log/fail2ban.log
action = f2b-iptables-allports
f2b-mqtt-action-banisher
findtime = 1d
bantime = 1d
maxretry = 3
# Niveau 2 : 3 bans en 1 semaine → ban permanent
[recidive-hard]
enabled = true
filter = recidive-filter
logpath = /var/log/fail2ban.log
action = f2b-iptables-allports
f2b-mqtt-action-banisher
findtime = 1w
bantime = -1
maxretry = 3
+15
View File
@@ -0,0 +1,15 @@
[sshd]
enabled = true
port = 22
maxretry = 3
bantime = 24h
[pam-generic]
enabled = true
maxretry = 3
# facility 10 = auth/security (PAM, su, sudo…)
journalmatch = SYSLOG_FACILITY=10
[mysqld-auth]
enabled = true
journalmatch = _SYSTEMD_UNIT=mariadb.service