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
+197
View File
@@ -0,0 +1,197 @@
# Fail2banActionBanishment
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.
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**.
Full documentation (installation, detailed production deployment,
token-based node enrollment, MQTT topics and commands reference):
`make docs-serve` then <http://127.0.0.1:8001/>, or `make docs` to
generate the static site into `site/`. See also [README.md](README.md)
for the French version.
## Architecture (overview)
```
[node] fail2ban/iptables → local mosquitto (1883) → master mosquitto (8883, TLS)
decision / correlation
[node] ◄──────────────── MQTT action (BAN, SYNC_BAN, UNBAN, ...) ◄──────────
```
## 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 is enough
(`make join-token` / `make join`, detailed below).
## Repository layout
- `generic/fail2ban/` — jails, filters and the MQTT action (`f2b-mqtt-action-banisher`)
- `generic/firewall/` — iptables launcher script (`firewall-launcher.sh`), per-service rules
- `generic/mosquitto/` — local MQTT broker (`127.0.0.1:1883`, no persistence) and master broker (`mosquitto-master.conf`, port 8883, mTLS)
- `generic/emitter/` — systemd service templates (web/ASGI, MQTT ingestion, Celery worker, master relay)
- `generic/supervisor/``.example.conf` templates (optional alternative to systemd, `make install-supervisor`)
- `generic/nginx/``.example.conf` TLS reverse-proxy template for the dashboard (not deployed automatically)
- `emitter/` — Django emitter (`banevents` app): MQTT ingestion (`manage.py mqtt_listen`), master relay (`manage.py master_client`), `BanEvent` model, dashboard + map + history, geolocation (Celery)
- `.env.example` — configuration template (copied to `.env` by `install.sh`, never committed)
- `install.sh` — idempotent deployment (packages, config files, systemd services, including the emitter); wrapped by the `make` targets below
- `scripts/test-fail2ban-filters.sh` — custom filter checks via `fail2ban-regex`
- `scripts/master-ca-init.sh`, `scripts/generate-node-cert.sh` — private CA and mTLS client certs for the master broker (manual flow, see token-based enrollment below for the recommended flow)
- `Makefile` — every entry point (`make help` for the full list)
- `docs/` — mkdocs documentation (installation, production deployment, reference)
## Installation (Debian 13 VPS)
Deployment model: **no copy into `/opt`**. The repo cloned into a dedicated
user's `$HOME` (e.g. `banisher`) *is* the deployment — venv, SQLite
database, `staticfiles/` and `.env` all live inside that checkout, owned by
that user. `install.sh` detects this user from the repo's actual owner (no
hardcoded name):
```sh
useradd -m -s /bin/bash banisher # once, if the account doesn't exist yet
su - banisher -c 'git clone <repo-url> ~/Fail2banMqttActionBanisher'
cd ~banisher/Fail2banMqttActionBanisher
sudo make install
sudo make status
```
`make install` (= `sudo ./install.sh install`):
- installs/checks a `NOPASSWD:ALL` sudoers entry for that user
(`/etc/sudoers.d/<user>-full-access`, created once if missing) — this
account should therefore be **dedicated to this project**, not shared with
other uses;
- copies `.env.example` to `.env` if missing (never regenerated afterward,
`DJANGO_SECRET_KEY` generated randomly) — edit `.env` for real credentials
(MQTT password for the `emitter` account, master domain, ...);
- creates the emitter's Python venv (`emitter/.venv`), applies migrations
and `collectstatic`, all run as the deployment user (never root);
- enables four systemd services: `fail2ban-emitter-web` (ASGI/Daphne
dashboard, `127.0.0.1:8050`), `fail2ban-emitter-mqtt`
(`manage.py mqtt_listen`), `fail2ban-emitter-worker` (Celery worker,
geolocation) and `fail2ban-emitter-master-client` (`manage.py
master_client`, relay/execution of the master's commands — won't
actually connect until a node certificate has been obtained, see
below).
Also edit `/etc/fail2ban/mqtt.conf` (real MQTT credentials, copied from
`generic/fail2ban/mqtt.conf.example` if it doesn't exist yet).
To update the emitter after a `git pull`, just rerun `sudo make install`:
dependencies/migrations are reapplied and all four services restarted.
Additional deployment options (detailed in the [deployment
documentation](docs/deployment.en.md), `make docs-serve` for the
browsable version):
- **master** (`sudo make install-master`) — public mTLS MQTT broker,
private CA, multi-node ingestion;
- **token-based node enrollment** (`make join-token` on the master, then
`sudo make join` on the new node) — recommended way to attach a node to
a master, without ever copying a certificate by hand;
- **nginx** (`sudo make install-nginx`) — TLS reverse proxy (Let's
Encrypt) in front of the dashboard, for public access;
- **supervisor** (`sudo make install-supervisor` / `sudo make
uninstall-supervisor`) — optional alternative to systemd;
- **MariaDB** (`sudo make install-mariadb`) — switches the database from
SQLite (default) to a dedicated local MariaDB server.
## Django emitter (development)
Requires a reachable Redis (Django Channels channel layer, e.g. `docker run
-p 6379:6379 redis`).
```sh
make run # dashboard + map at http://127.0.0.1:8000/ (real-time WebSocket included)
make mqtt # in a second terminal: MQTT ingestion
make worker # in a third terminal: asynchronous geolocation
```
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.
Geolocation uses the free ip-api.com fallback API until a local GeoLite2
(MaxMind) database is configured.
## Translations (fr/en)
French is the source language of the template code; English is a
translation maintained in `emitter/locale/en/LC_MESSAGES/django.po`. A
selector in the dashboard's sidebar switches the active language
(`django_language` cookie).
After adding/changing a `{% trans %}`/`{% blocktrans %}` in a template:
```sh
cd emitter
.venv/bin/python manage.py makemessages -l en --no-location --ignore ".venv"
# edit locale/en/LC_MESSAGES/django.po (fill in the missing msgstr entries)
.venv/bin/python manage.py compilemessages -l en --ignore ".venv"
```
`--ignore ".venv"`: without it, `compilemessages` also recompiles (slowly,
with no useful effect) every one of Django's own bundled translations found
inside the venv, since it lives under `emitter/` — harmless but pointless.
## Master broker and node enrollment
See the [deployment documentation](docs/deployment.en.md) for the full
detail: master provisioning, the enrollment security model (single-use
token, private key that never leaves the node, token automatically given
back on a transient failure), troubleshooting. In short:
```sh
# on the master
make join-token NODE=my-node
# on the new node
sudo make join MASTER=https://master-dashboard NODE=my-node TOKEN=<token>
sudo make install
```
Master-side ingestion (`manage.py master_listen`) systematically
republishes a `SYNC_BAN` for every ingested ban, and correlates
independent bans across nodes to trigger an automatic escalation — see
the [reference](docs/reference.en.md) for the full list of master
commands and MQTT topics.
## Development constraints
- Source code in English, comments in French
- Indentation: 4 spaces, never tabs, see `.editorconfig`
(exception: Makefile recipes, which must be tab-indented — a constraint of
Make itself)
- Python type-checked with `pyright` (`pyrightconfig.json`), shell scripts
with `shellcheck``make lint`, also run in CI
- JavaScript: ES6 classes
## License
AGPL-3.0 — see [LICENSE](LICENSE).