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
+189
View File
@@ -0,0 +1,189 @@
# Production deployment
This page details deploying a master + nodes set in production, beyond
the single isolated node covered in [Installation](installation.en.md).
## Overview
Three ways to use this project, from simplest to most complete — the
same base install (`sudo make install`) is the foundation for all three:
- **Solo**: `sudo make install`, alone. This server detects and bans
locally, without centralizing anything elsewhere. Enough to protect a
single VPS. The relay service to a master
(`fail2ban-emitter-master-client`) still runs, but silently keeps
retrying in a loop as long as no master is configured (no harm done,
`Restart=on-failure`).
- **Solo + master**: `sudo make install` **then** `sudo make
install-master`, on the **same** server. This VPS protects its own
services (like solo mode) and also serves as a master, ready to take on
other nodes later — useful for starting out alone today without
closing the door on a second server tomorrow.
- **Master + nodes** (several VPSes): one server in solo+master mode, and
one or more other servers, each installed solo and then attached to
that master via token-based enrollment (below). The master itself
stays a node like any other (it detects and bans locally in addition
to centralizing the decision) — going from "solo+master on its own" to
"master + nodes" therefore requires no reinstall of the master, just
adding nodes to it over time.
Recommended order for moving to several nodes:
1. Deploy the master (once — solo+master if this same server should also
protect itself, which is the common case).
2. On the master, issue a token for each new node.
3. On each new node, enroll using that token.
## Deploying the master
```sh
sudo make install-master
```
Requires a first `sudo make install` already done on this same machine.
Provisions:
- a dedicated **private CA** (`scripts/master-ca-init.sh`), distinct from
the Let's Encrypt certificate that serves the broker's TLS identity —
it only signs nodes' client certificates (mTLS authentication);
- the public Mosquitto listener (port 8883, TLS, `MQTT_MASTER_DOMAIN` in
`.env`) with per-node ACLs;
- master-side ingestion (`fail2ban-emitter-master-listen`, runs with its
own mTLS identity `master-internal`, generated automatically).
`MQTT_MASTER_DOMAIN` must point to a real public domain (DNS already set
up) — the broker obtains its Let's Encrypt certificate for that name.
## Token-based node enrollment (recommended)
### The problem
Adding a node by hand would require generating a key + certificate on the
master, then copying those files to the new node (`scp`), renaming them
correctly, and editing its `.env` — several manual, cross-machine steps,
and the private key would travel over the network (never a good
practice).
### The model
The master issues a **single-use token**, short-lived (1h by default).
The new node uses that token to enroll itself:
- The node generates its **own private key locally** and never sends it
— only a CSR (signing request) goes to the master.
- The token is never stored in plaintext on the master: only its SHA-256
hash is persisted (same logic as a password hash).
- The token is **atomically marked used** as soon as it's validated, and
**automatically given back** if a later step fails (inconsistent CSR,
signing failure) — a transient hiccup doesn't force issuing an
entirely new token.
- The CN of the received CSR must match the node name declared when the
token was issued — a stolen token can't be used to claim a different
name.
- Any error returned to the client is **deliberately generic**
("invalid or expired token"), regardless of the actual cause — the
detail (unknown, expired, or already-used token, inconsistent CN,
signing failure) only goes into the master's logs, so as to give no
hint to a third party probing the endpoint.
- The transport is the master's HTTPS dashboard (already publicly
exposed, nginx + Let's Encrypt): no new port or service to open, and it
solves the chicken-and-egg problem (a node can't use mTLS MQTT to
obtain... its first mTLS certificate).
### Step by step
On the **master**:
```sh
make join-token NODE=node-name
```
Prints the plaintext token **only once**, along with the full command to
run on the new node (lost token: simply issue a new one, the previous
one stays valid independently until it expires).
On the **new node** (having already gone through `sudo make install`):
```sh
sudo make join MASTER=https://<master-domain> NODE=node-name TOKEN=<token>
sudo make install
```
The second `make install` applies the received configuration
(`MQTT_MASTER_NODE_NAME` in `.env`, certificate in place) and restarts
the services.
### What happens behind the scenes
```
[new node] [master]
generates key + CSR (local, never sent)
POST /api/join/ {node_name, token, csr} ──►
validates the token (hash, expiry, single use)
checks the CSR's CN == node_name
signs the CSR with the private CA
adds the ACL entitlement for this node
restarts mosquitto
{cert, ca_cert} ◄────
writes cert/ca_cert, updates .env
```
### Troubleshooting
- **"Invalid or expired token"** — the message is deliberately generic.
Possible causes: token already used (run `make join-token` again to
issue a new one), token expired (1h default, adjustable via
`make join-token NODE=... TTL=<minutes>`), or a typo in the node name
(must be identical between token issuance and `make join`).
- The new node must be able to reach the master over HTTPS (443) — a
firewall blocking outbound traffic would prevent enrollment.
## Databases
SQLite is the default, nothing to configure. Two alternatives:
```sh
sudo make install-mariadb # provisions a local MariaDB server + dedicated database/user
sudo make install # re-run afterward to apply migrations on the new database
```
PostgreSQL is supported at the configuration level (`DB_ENGINE=postgresql`
in `.env`, driver `emitter/requirements-postgresql.txt`) but without an
automated provisioning subcommand for now — server and database must be
created manually before switching `DB_ENGINE`.
Migrating data from one backend to another (engine-agnostic dump via
`manage.py dumpdata`/`loaddata`):
```sh
make dump-db # writes emitter/db-dump-<date>.json (git-ignored, contains real data)
# ... switch DB_ENGINE, migrate ...
emitter/.venv/bin/python emitter/manage.py loaddata <file>.json
```
## nginx reverse proxy + public TLS
```sh
sudo make install-nginx # reads DASHBOARD_DOMAIN from .env
```
Obtains a Let's Encrypt certificate and deploys an nginx vhost in front of
the dashboard (WebSocket included) — needed for public access; without
it, the dashboard is only reachable locally/over VPN.
## Alternative to systemd: supervisor
```sh
sudo make install-supervisor # switches the emitter's 4 services to supervisor
sudo make uninstall-supervisor # rollback (re-enables systemd)
```
## Updating
```sh
git pull
sudo make install
```
Reapplies dependencies/migrations and restarts the affected services, on
any node (including the master).
+193
View File
@@ -0,0 +1,193 @@
# Déploiement en production
Cette page détaille le déploiement d'un ensemble master + noeuds en
production, au-delà du premier noeud isolé couvert par
[Installation](installation.md).
## Vue d'ensemble
Trois façons d'utiliser ce projet, du plus simple au plus complet — la
même installation de base (`sudo make install`) sert de socle aux trois :
- **Solo** : `sudo make install`, seul. Ce serveur détecte et bannit
localement, sans rien centraliser ailleurs. Suffisant pour protéger un
unique VPS. Le service de relais vers un master
(`fail2ban-emitter-master-client`) tourne quand même, mais réessaie
silencieusement en boucle tant qu'aucun master n'est configuré (aucune
gêne, `Restart=on-failure`).
- **Solo + master** : `sudo make install` **puis** `sudo make
install-master`, sur le **même** serveur. Ce VPS protège ses propres
services (comme en mode solo) et sert en plus de master, prêt à
accueillir d'autres noeuds plus tard — utile pour démarrer seul
aujourd'hui sans fermer la porte à un second serveur demain.
- **Master + noeuds** (plusieurs VPS) : un serveur en mode solo+master,
et un ou plusieurs autres serveurs, chacun installé en solo puis
rattaché à ce master via l'auto-inscription par jeton (ci-dessous). Le
master lui-même reste un noeud comme les autres (il détecte et bannit
localement en plus de centraliser la décision) — passer de "solo+master
tout seul" à "master + noeuds" ne demande donc aucune réinstallation du
master, seulement d'y ajouter des noeuds au fur et à mesure.
Ordre recommandé pour passer à plusieurs noeuds :
1. Déployer le master (une seule fois — solo+master si ce même serveur
doit aussi se protéger lui-même, ce qui est le cas courant).
2. Sur le master, émettre un jeton pour chaque nouveau noeud.
3. Sur chaque nouveau noeud, s'auto-inscrire avec ce jeton.
## Déployer le master
```sh
sudo make install-master
```
Nécessite un premier `sudo make install` déjà effectué sur cette même
machine. Provisionne :
- une **CA privée** dédiée (`scripts/master-ca-init.sh`), distincte du
certificat Let's Encrypt qui sert l'identité TLS du broker — elle ne
sert qu'à signer les certificats clients des noeuds (authentification
mTLS) ;
- le listener Mosquitto public (port 8883, TLS, `MQTT_MASTER_DOMAIN` dans
`.env`) avec ACL par noeud ;
- l'ingestion côté master (`fail2ban-emitter-master-listen`, tourne avec
sa propre identité mTLS `master-internal`, générée automatiquement).
`MQTT_MASTER_DOMAIN` doit pointer vers un domaine public réel (DNS déjà en
place) — le broker obtient son certificat Let's Encrypt à ce nom.
## Auto-inscription d'un noeud (recommandé)
### Le problème
Ajouter un noeud à la main demanderait de générer une clé + un certificat
sur le master, puis de copier ces fichiers vers le nouveau noeud (`scp`),
de les renommer correctement, et d'éditer son `.env` — plusieurs étapes
manuelles, cross-machine, et la clé privée transiterait par le réseau
(jamais une bonne pratique).
### Le modèle
Le master émet un **jeton à usage unique**, à courte durée de vie (1h par
défaut). Le nouveau noeud utilise ce jeton pour s'inscrire lui-même :
- Le noeud génère sa **propre clé privée localement** et ne l'envoie
jamais — seule une CSR (demande de signature) part vers le master.
- Le jeton n'est jamais stocké en clair côté master : seul son hash
SHA-256 est persisté (même logique qu'un hachage de mot de passe).
- Le jeton est **marqué utilisé de façon atomique** dès sa validation, et
**rendu automatiquement** si une étape suivante échoue (CSR
incohérente, échec de signature) — un accroc transitoire ne force pas
à réémettre un jeton entièrement nouveau.
- Le CN de la CSR reçue doit correspondre au nom de noeud déclaré au
moment de l'émission du jeton — un jeton volé ne permet pas de réclamer
un autre nom.
- Toute erreur renvoyée au client est **volontairement générique**
("jeton invalide ou expiré"), quelle que soit la cause réelle — le
détail (jeton inconnu, expiré, déjà utilisé, CN incohérent, échec de
signature) part uniquement dans les journaux du master, pour ne donner
aucun indice à un tiers qui sonderait l'endpoint.
- Le transport est le tableau de bord HTTPS du master (déjà exposé
publiquement, nginx + Let's Encrypt) : aucun nouveau port ni service à
ouvrir, et ça résout le problème d'oeuf-et-poule (un noeud ne peut pas
utiliser mTLS MQTT pour obtenir... son premier certificat mTLS).
### Étape par étape
Sur le **master** :
```sh
make join-token NODE=nom-du-noeud
```
Affiche le jeton en clair **une seule fois**, avec la commande complète à
lancer sur le nouveau noeud (jeton perdu : simplement en émettre un
nouveau, celui-ci reste valide indépendamment jusqu'à expiration).
Sur le **nouveau noeud** (déjà passé par `sudo make install` au
préalable) :
```sh
sudo make join MASTER=https://<domaine-du-master> NODE=nom-du-noeud TOKEN=<jeton>
sudo make install
```
Le second `make install` applique la configuration reçue
(`MQTT_MASTER_NODE_NAME` dans `.env`, certificat en place) et redémarre
les services.
### Ce qui se passe en coulisse
```
[nouveau noeud] [master]
génère clé + CSR (locale, jamais transmise)
POST /api/join/ {node_name, token, csr} ──►
vérifie le jeton (hash, expiration, usage unique)
vérifie le CN de la CSR == node_name
signe la CSR via la CA privée
ajoute l'entitlement ACL pour ce noeud
redémarre mosquitto
{cert, ca_cert} ◄────
écrit cert/ca_cert, met à jour .env
```
### Dépannage
- **"Jeton invalide ou expiré"** — le message est volontairement
générique. Causes possibles : jeton déjà utilisé (relancer
`make join-token` pour en émettre un nouveau), jeton expiré (durée par
défaut 1h, ajustable via `make join-token NODE=... TTL=<minutes>`), ou
faute de frappe dans le nom de noeud (doit être identique entre
l'émission du jeton et `make join`).
- Le nouveau noeud doit pouvoir joindre le master en HTTPS (443) — un
pare-feu qui bloquerait la sortie empêcherait l'auto-inscription.
## Bases de données
SQLite est le défaut, sans rien à configurer. Deux alternatives :
```sh
sudo make install-mariadb # provisionne un serveur MariaDB local + base/utilisateur dédiés
sudo make install # à relancer ensuite pour appliquer les migrations sur la nouvelle base
```
PostgreSQL est supporté côté configuration (`DB_ENGINE=postgresql` dans
`.env`, driver `emitter/requirements-postgresql.txt`) mais sans
sous-commande de provisionnement automatique pour l'instant — serveur et
base à créer manuellement avant de basculer `DB_ENGINE`.
Portage d'une base vers une autre (dump indépendant du moteur, via
`manage.py dumpdata`/`loaddata`) :
```sh
make dump-db # écrit emitter/db-dump-<date>.json (exclu de git, contient des données réelles)
# ... bascule DB_ENGINE, migrate ...
emitter/.venv/bin/python emitter/manage.py loaddata <fichier>.json
```
## Reverse proxy nginx + TLS public
```sh
sudo make install-nginx # lit DASHBOARD_DOMAIN dans .env
```
Obtient un certificat Let's Encrypt et déploie un vhost nginx devant le
tableau de bord (WebSocket inclus) — nécessaire pour un accès public ;
sans ça, le tableau de bord n'est accessible qu'en local/VPN.
## Alternative à systemd : supervisor
```sh
sudo make install-supervisor # bascule les 4 services de l'émetteur vers supervisor
sudo make uninstall-supervisor # retour arrière (réactive systemd)
```
## Mise à jour
```sh
git pull
sudo make install
```
Réapplique dépendances/migrations et redémarre les services concernés,
sur n'importe quel noeud (y compris le master).
+60
View File
@@ -0,0 +1,60 @@
# Fail2banActionBanisher
Distributed malicious-host banishing system built on **fail2ban** and
**iptables**, with event propagation over **MQTT**.
Each node detects and bans locally (fail2ban + iptables), publishes the
event to a local Mosquitto broker, then relays it to a private **master**
broker (mTLS). The master centralizes the decision (multi-node
correlation, global recidive detection, shared reputation) and
republishes the actions to be executed to all subscribed nodes.
Target machine: **Debian 13 VPS**.
## Architecture (overview)
```
[node] fail2ban/iptables → local mosquitto (1883) → master mosquitto (8883, TLS)
decision / correlation
[node] ◄──────────────── MQTT action (BAN, SYNC_BAN, UNBAN, ...) ◄──────────
```
A Django dashboard (real-time, WebSocket) runs on every node and on the
master, with an aggregated multi-node view on the master side.
## Use cases
The master/nodes model doesn't assume any ownership relationship between
the protected servers — only that they share a common decision authority.
That makes the architecture relevant beyond a single operator running
their own VPSes:
- **A set of sites** (several VPSes run by the same operator, each
hosting one or more services): an IP attacking one site gets banned
everywhere via `SYNC_BAN`, without manual per-site monitoring.
- **A community** (several independent administrators, each responsible
for their own server, who agree to pool their detections): every
member keeps their own node and their own local fail2ban — only ban
propagation is shared through the common master. No member gets SSH
access to another member's server.
- **An IT services company** managing several clients' sites: a
centralized master gives an aggregated view (jail/country counters,
per-node history, per-client aliases) without mixing client data —
each node only sees its own events, the master sees everything.
In all three cases, a new server joins the set without ever handing out
SSH access to the others: a single-use [token-based
enrollment](deployment.en.md#token-based-node-enrollment-recommended) is
enough — particularly relevant for the community and services-company
cases, where the servers belong to different parties.
## Where to go next
- [Installation](installation.en.md) — development setup, first
production node.
- [Production deployment](deployment.en.md) — master, token-based node
enrollment, databases, reverse proxy, alternatives to systemd.
- [Reference](reference.en.md) — MQTT topics, master commands, custom
fail2ban jails.
+62
View File
@@ -0,0 +1,62 @@
# Fail2banActionBanishment
Système distribué de bannissement d'hôtes malveillants basé sur **fail2ban**
et **iptables**, avec propagation des événements par **MQTT**.
Chaque noeud détecte et bannit localement (fail2ban + iptables), publie
l'événement sur un broker Mosquitto local, puis relaie l'information vers un
broker **master** privé (mTLS). Le master centralise la décision
(corrélation multi-noeuds, récidive globale, réputation partagée) et
republie les actions à exécuter vers l'ensemble des noeuds abonnés.
Machine cible : **VPS Debian 13**.
## Architecture (résumé)
```
[noeud] fail2ban/iptables → mosquitto local (1883) → mosquitto master (8883, TLS)
décision / corrélation
[noeud] ◄──────────────── action MQTT (BAN, SYNC_BAN, UNBAN, ...) ◄──────────
```
Un tableau de bord Django (temps réel, WebSocket) tourne sur chaque noeud et
sur le master, avec une vue agrégée multi-noeuds côté master.
## Cas d'usage
Le modèle master/noeuds ne suppose aucun lien d'appartenance entre les
serveurs protégés — seulement qu'ils partagent une même autorité de
décision. Ça rend l'architecture pertinente au-delà d'un opérateur unique
sur ses propres VPS :
- **Un ensemble de sites** (plusieurs VPS d'un même opérateur, chacun
hébergeant un ou plusieurs services) : une IP qui attaque un site est
bannie partout via `SYNC_BAN`, sans surveillance manuelle site par site.
- **Une communauté** (plusieurs administrateurs indépendants, chacun
responsable de son propre serveur, qui acceptent de mutualiser leurs
détections) : chaque membre garde son propre noeud et son propre
fail2ban local — seule la propagation des bans est partagée via le
master commun. Aucun accès aux serveurs des autres membres n'est requis
ni accordé.
- **Une société de services informatiques** qui gère les sites de
plusieurs clients : un master centralisé donne une vue agrégée
(compteurs jail/pays, historique par noeud, alias par client) sans
mélanger les données de client à client — chaque noeud ne voit que ses
propres événements, le master voit tout.
Dans les trois cas, un nouveau serveur rejoint l'ensemble sans jamais
donner accès SSH aux autres : une [auto-inscription par jeton à usage
unique](deployment.md#auto-inscription-dun-noeud-recommandé) suffit —
pertinent en particulier pour la communauté et la société de services, où
les serveurs appartiennent à des tiers différents.
## Pour aller plus loin
- [Installation](installation.md) — mise en route en développement, premier
noeud en production.
- [Déploiement en production](deployment.md) — master, auto-inscription
d'un noeud, bases de données, reverse proxy, alternatives à systemd.
- [Référence](reference.md) — topics MQTT, commandes du master, jails
fail2ban personnalisées.
+73
View File
@@ -0,0 +1,73 @@
# Installation
## Prerequisites
- **Debian 13** VPS, root access (or an account with `sudo`).
- The repository cloned into the `$HOME` of a **non-root** user dedicated
to this project (e.g. `banisher`) — `install.sh`/`make install`
determine this user from the repository's actual owner
(`stat -c '%U' <repo>`), never hardcoded. Trade-off accepted: the user
gets full `sudo` access (`NOPASSWD:ALL`), to be reserved for an account
strictly dedicated to this project.
```sh
git clone <repo-url> ~/Fail2banMqttActionBanisher
cd ~/Fail2banMqttActionBanisher
```
## Development (without real fail2ban/iptables)
Requires a reachable Redis (Django Channels channel layer), e.g.
`docker run -p 6379:6379 redis`.
```sh
make run # dashboard + map, http://127.0.0.1:8000/
make mqtt # in a second terminal: MQTT ingestion
make worker # in a third terminal: async geolocation
```
These targets automatically create the Python venv (`emitter/.venv`) and
install dependencies on first run.
Environment variables (`MQTT_BROKER_HOST`, `MQTT_BROKER_USERNAME`,
`MQTT_BROKER_PASSWORD`, `REDIS_HOST`, `REDIS_PORT`, ...): copy
`.env.example` to `.env` at the repo root and edit it — loaded
automatically by Django (`python-dotenv`), nothing to export by hand.
## First production node
!!! warning "Must be adapted before `make install`"
`generic/firewall/firewall-filters.conf` and the files under
`generic/fail2ban/jail.d/` come from the reference server
(`miraceti-vps1`, a master) and contain rules specific to it
(OpenVPN, Coturn, WireGuard, Portainer, a hardcoded public IP,
`DOCKER-USER`...). On a plain node, keep only what's actually
exposed (typically SSH + 80/443) — the unneeded sections reference
interfaces (`$VPN_IF`, `$WG_IF`) that don't exist on a node, which
produces silent `iptables` errors (the script keeps going, but the
intended rule is never applied). Also check `ETH_IF` in
`generic/firewall/firewall-launcher.sh` (real interface name:
`ip -o link show`), and disable `ufw` if present (having both
`iptables` rulesets active at once breaks one of them in
unpredictable ways).
```sh
sudo make install
sudo make status
```
Deploys and enables: system packages (fail2ban, iptables, mosquitto,
Redis), firewall, custom fail2ban jails, local Mosquitto broker, and the
Django emitter's four services (web, MQTT ingestion, Celery worker,
master relay). Idempotent: re-running `sudo make install` after a
`git pull` reapplies dependencies/migrations and restarts the affected
services.
Edit `/etc/fail2ban/mqtt.conf` (real MQTT credentials, copied from
`generic/fail2ban/mqtt.conf.example` if it doesn't exist yet) before the
first deployment.
A freshly installed node already works standalone (local detection and
banning) ; the relay to a master (`fail2ban-emitter-master-client`) will
keep retrying in a loop until a node certificate has been obtained — see
[Production deployment](deployment.en.md) for what's next.
+74
View File
@@ -0,0 +1,74 @@
# Installation
## Prérequis
- VPS **Debian 13**, accès root (ou un compte avec `sudo`).
- Le dépôt cloné dans le `$HOME` d'un utilisateur **non-root** dédié à ce
projet (ex. `banisher`) — `install.sh`/`make install` déterminent cet
utilisateur à partir du propriétaire réel du dépôt
(`stat -c '%U' <dépôt>`), jamais codé en dur. Contrepartie assumée :
l'utilisateur reçoit un accès `sudo` complet (`NOPASSWD:ALL`), à
réserver à un compte strictement dédié à ce projet.
```sh
git clone <url-du-dépôt> ~/Fail2banMqttActionBanisher
cd ~/Fail2banMqttActionBanisher
```
## Développement (sans fail2ban/iptables réels)
Nécessite un Redis accessible (channel layer Django Channels), par exemple
`docker run -p 6379:6379 redis`.
```sh
make run # tableau de bord + carte, http://127.0.0.1:8000/
make mqtt # dans un second terminal : ingestion MQTT
make worker # dans un troisième terminal : géolocalisation asynchrone
```
Ces cibles créent automatiquement le venv Python (`emitter/.venv`) et
installent les dépendances au premier lancement.
Variables d'environnement (`MQTT_BROKER_HOST`, `MQTT_BROKER_USERNAME`,
`MQTT_BROKER_PASSWORD`, `REDIS_HOST`, `REDIS_PORT`, ...) : copier
`.env.example` vers `.env` à la racine du dépôt et l'éditer — chargé
automatiquement par Django (`python-dotenv`), rien à exporter à la main.
## Premier noeud en production
!!! warning "À adapter impérativement avant `make install`"
`generic/firewall/firewall-filters.conf` et les fichiers de
`generic/fail2ban/jail.d/` proviennent du serveur de référence
(`miraceti-vps1`, un master) et contiennent des règles qui lui sont
propres (OpenVPN, Coturn, WireGuard, Portainer, IP publique codée en
dur, `DOCKER-USER`...). Sur un simple noeud, ne garder que ce qui est
réellement exposé (typiquement SSH + 80/443) — les sections
superflues réfèrent des interfaces (`$VPN_IF`, `$WG_IF`) absentes
d'un noeud, ce qui produit des erreurs `iptables` silencieuses (le
script continue, mais la règle voulue n'est jamais posée). Vérifier
aussi `ETH_IF` dans `generic/firewall/firewall-launcher.sh` (nom
réel de l'interface : `ip -o link show`) et désactiver `ufw` s'il est
présent (coexister avec les règles `iptables` posées ici casserait
l'un des deux jeux de règles de façon imprévisible).
```sh
sudo make install
sudo make status
```
Déploie et active : paquets système (fail2ban, iptables, mosquitto,
Redis), pare-feu, jails fail2ban personnalisées, broker Mosquitto local,
et les quatre services de l'émetteur Django (web, ingestion MQTT, worker
Celery, relais vers le master). Idempotent : relancer `sudo make install`
après un `git pull` réapplique dépendances/migrations et redémarre les
services concernés.
Éditer `/etc/fail2ban/mqtt.conf` (identifiants MQTT réels, copié depuis
`generic/fail2ban/mqtt.conf.example` s'il n'existe pas encore) avant le
premier déploiement.
Un noeud fraîchement installé fonctionne déjà en autonome (détection et
ban locaux) ; le relais vers un master (`fail2ban-emitter-master-client`)
tentera de se connecter en boucle tant qu'aucun certificat de noeud n'a
été obtenu — voir [Déploiement en production](deployment.md) pour la
suite.
+61
View File
@@ -0,0 +1,61 @@
# Reference
## MQTT topics
| Topic | Direction | Role |
|---|---|---|
| `fail2ban/<node>/ban` | node → master | ban/notice event published by this node |
| `fail2ban/<node>/heartbeat` | node → master | periodic liveness proof (independent of any ban activity) |
| `fail2ban/<node>/ack` | node → master | acknowledgement after executing a command received from the master |
| `fail2ban/<node>/action` | master → node | command addressed specifically to this node |
| `fail2ban/broadcast/action` | master → all nodes | command broadcast to all subscribed nodes |
| `fail2ban/broadcast/roster` | master → all nodes | list of known nodes + global jail/country counters, periodic |
`<node>` in the topic segment is the mTLS client certificate's CN
(enforced by the broker's ACL) — the node's internal identifier
(`uuid.getnode()`) travels in the message body, not in the topic.
## Master commands
| Command | Effect on the receiving node | Trigger |
|---|---|---|
| `SYNC_BAN` | Full ban (all ports) via a dedicated jail | Automatic: the master systematically publishes a `SYNC_BAN` for every ban it ingests |
| `ESCALATE` | **Permanent** ban via a dedicated jail | Automatic: the master correlates — an IP independently banned on several distinct nodes within 24h triggers an escalation |
| `BAN_ALLPORTS` | Full ban (all ports), same mechanism as `SYNC_BAN` | Manual (targeted human decision) |
| `WHITELIST` | Added to `ignoreip` on all active jails + unbanned — in-memory, not persistent | Manual |
| `RATE_LIMIT` | Throttle (10 req/s, burst 20) instead of a full block | Manual — edge case, likely false positives |
| `NOTIFY_ONLY` | No network action, just a visible dashboard notification | Manual — observation mode |
| `REPORT_ABUSE` | Report to AbuseIPDB — **not broadcast to nodes**, submitted directly by the master (one report per IP is enough) | Manual, dry-run until `ABUSEIPDB_ENABLED`/`ABUSEIPDB_API_KEY` are configured |
Manual commands are triggered on the master, by hand:
```sh
emitter/.venv/bin/python emitter/manage.py publish_command WHITELIST 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command BAN_ALLPORTS 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command RATE_LIMIT 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command REPORT_ABUSE 203.0.113.5
```
Geolocation of banned IPs is automatic (asynchronous, on ingestion of
each event); to retry the ones left without coordinates (API failure,
private IP):
```sh
emitter/.venv/bin/python emitter/manage.py retry_geolocation # all ungeolocated events
emitter/.venv/bin/python emitter/manage.py retry_geolocation --ip <ip> # a specific IP
```
## Custom fail2ban jails
| Jail | Role |
|---|---|
| `emitter-admin-auth` | Dashboard `/admin/` lockout (django-axes) → network ban |
| `master-sync` | Receiving a `SYNC_BAN`/`BAN_ALLPORTS` — never triggered by a log, only by manual injection from the master relay |
| `master-ratelimit` | Receiving a `RATE_LIMIT` |
| `master-escalate` | Receiving an `ESCALATE` — permanent ban |
`emitter-admin-auth` follows a reusable template
(`generic/fail2ban/jail.d/app-auth.conf.template` +
`generic/fail2ban/filter.d/app-auth.conf`) to protect any other
application exposed on the same server the same way, not just this
dashboard.
+61
View File
@@ -0,0 +1,61 @@
# Référence
## Topics MQTT
| Topic | Sens | Rôle |
|---|---|---|
| `fail2ban/<noeud>/ban` | noeud → master | événement de ban/notice publié par ce noeud |
| `fail2ban/<noeud>/heartbeat` | noeud → master | preuve de vie périodique (indépendante de toute activité de ban) |
| `fail2ban/<noeud>/ack` | noeud → master | accusé de réception après exécution d'une commande reçue du master |
| `fail2ban/<noeud>/action` | master → noeud | commande adressée spécifiquement à ce noeud |
| `fail2ban/broadcast/action` | master → tous les noeuds | commande diffusée à tous les noeuds abonnés |
| `fail2ban/broadcast/roster` | master → tous les noeuds | liste des noeuds connus + compteurs jail/pays globaux, périodique |
`<noeud>` dans le segment de topic est le CN du certificat client mTLS
(imposé par l'ACL du broker) — l'identifiant interne du noeud
(`uuid.getnode()`) voyage dans le corps du message, pas dans le topic.
## Commandes du master
| Commande | Effet sur le noeud qui la reçoit | Déclenchement |
|---|---|---|
| `SYNC_BAN` | Ban total (toutes ports) via une jail dédiée | Automatique : le master publie systématiquement un `SYNC_BAN` pour chaque ban qu'il ingère |
| `ESCALATE` | Ban **permanent** via une jail dédiée | Automatique : le master corrèle — une IP bannie indépendamment sur plusieurs noeuds distincts en 24h déclenche une escalade |
| `BAN_ALLPORTS` | Ban total (toutes ports), même mécanisme que `SYNC_BAN` | Manuel (décision humaine ciblée) |
| `WHITELIST` | Ajout à `ignoreip` sur toutes les jails actives + débannit — en mémoire, non persistant | Manuel |
| `RATE_LIMIT` | Throttle (10 req/s, burst 20) au lieu d'un blocage total | Manuel — cas limite, faux positifs probables |
| `NOTIFY_ONLY` | Aucune action réseau, juste une notification visible sur le tableau de bord | Manuel — mode observation |
| `REPORT_ABUSE` | Signalement à AbuseIPDB — **pas diffusé aux noeuds**, soumis directement par le master (un rapport par IP suffit) | Manuel, dry-run tant que `ABUSEIPDB_ENABLED`/`ABUSEIPDB_API_KEY` ne sont pas configurés |
Les commandes manuelles se déclenchent sur le master, à la main :
```sh
emitter/.venv/bin/python emitter/manage.py publish_command WHITELIST 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command BAN_ALLPORTS 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command RATE_LIMIT 203.0.113.5
emitter/.venv/bin/python emitter/manage.py publish_command REPORT_ABUSE 203.0.113.5
```
La géolocalisation des IP bannies est automatique (asynchrone, dès
l'ingestion d'un événement) ; pour relancer celles restées sans
coordonnées (échec d'API, IP privée) :
```sh
emitter/.venv/bin/python emitter/manage.py retry_geolocation # tous les événements non géolocalisés
emitter/.venv/bin/python emitter/manage.py retry_geolocation --ip <ip> # une IP précise
```
## Jails fail2ban personnalisées
| Jail | Rôle |
|---|---|
| `emitter-admin-auth` | Verrouillage `/admin/` du tableau de bord (django-axes) → ban réseau |
| `master-sync` | Réception d'un `SYNC_BAN`/`BAN_ALLPORTS` — jamais déclenchée par un log, seulement par injection manuelle depuis le relais master |
| `master-ratelimit` | Réception d'un `RATE_LIMIT` |
| `master-escalate` | Réception d'un `ESCALATE` — ban permanent |
`emitter-admin-auth` suit un gabarit réutilisable
(`generic/fail2ban/jail.d/app-auth.conf.template` +
`generic/fail2ban/filter.d/app-auth.conf`) pour protéger de la même façon
n'importe quelle autre application exposée sur le même serveur, pas
seulement ce tableau de bord.