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
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# certbot-deploy-mosquitto.sh — copie le certificat Let's Encrypt vers un
# emplacement lisible par l'utilisateur système 'mosquitto' (non-root), et
# redémarre mosquitto pour qu'il recharge le nouveau certificat.
#
# /etc/letsencrypt est verrouillé root-only par certbot (le dossier lui-même,
# pas seulement privkey.pem) : mosquitto ne peut donc jamais lire les
# fichiers directement à leur emplacement d'origine, quels que soient leurs
# propres droits. D'où cette copie découplée, dans un répertoire que install.sh
# rend group-lisible pour 'mosquitto'.
#
# Installé par install.sh (install-master) comme deploy-hook certbot :
# /etc/letsencrypt/renewal-hooks/deploy/ — s'exécute automatiquement à
# chaque renouvellement. Peut aussi être lancé à la main.
set -euo pipefail
DOMAIN="__MQTT_MASTER_DOMAIN__"
SRC="/etc/letsencrypt/live/$DOMAIN"
DEST="/etc/mosquitto/master-tls"
install -d -o root -g mosquitto -m 750 "$DEST"
cp "$SRC/fullchain.pem" "$DEST/fullchain.pem"
cp "$SRC/privkey.pem" "$DEST/privkey.pem"
chown root:mosquitto "$DEST/fullchain.pem" "$DEST/privkey.pem"
chmod 640 "$DEST/fullchain.pem" "$DEST/privkey.pem"
systemctl restart mosquitto.service
+38
View File
@@ -0,0 +1,38 @@
# ACL du broker master — %u est le CN du certificat client (cf.
# use_identity_as_username dans mosquitto-master.conf). Chaque noeud ne peut
# publier que sous son propre topic de ban, et ne lire que ses propres
# commandes plus le canal de diffusion générale.
#
# Déployé par install.sh (install-master) vers /etc/mosquitto/master-acl.conf
pattern write fail2ban/%u/ban
# heartbeat (Phase 3, supervision) : chaque noeud publie périodiquement
# pour prouver qu'il est vivant, indépendamment de toute activité de ban.
# ack (Phase 2.b) : accusé de réception publié après exécution réelle d'une
# commande reçue du master (ex. SYNC_BAN), succès ou échec.
pattern write fail2ban/%u/heartbeat
pattern write fail2ban/%u/ack
pattern read fail2ban/%u/action
# Topic fixe (identique pour tous les noeuds) : "topic", pas "pattern" —
# "pattern" est réservé aux entrées substituées par client (%u/%c) et
# mosquitto avertit (pattern ignoré) si on l'utilise sans placeholder.
topic read fail2ban/broadcast/action
# Identité dédiée à l'ingestion/décision côté master
# (banevents.master_listen) : lecture de fail2ban/+/ban (TOUS les noeuds),
# et écriture sur le canal de diffusion (pour publier un SYNC_BAN) — deux
# droits plus larges que ceux d'un noeud pris individuellement (pattern
# ci-dessus, limité à fail2ban/%u/ban). "user <nom>" scope les règles
# suivantes à cette seule identité de certificat — généré via
# scripts/generate-node-cert.sh master-internal.
user master-internal
topic read fail2ban/+/ban
topic read fail2ban/+/heartbeat
topic read fail2ban/+/ack
topic write fail2ban/broadcast/action
# Roster périodique (Phase 3) : liste des noeuds connus + compteurs
# jail/pays globaux, republié localement par chaque noeud (NodeRegistry +
# cache Redis, voir banevents/stats.py) — sans ça la sidebar d'un noeud
# client n'aurait jamais que ses propres données.
topic write fail2ban/broadcast/roster
+41
View File
@@ -0,0 +1,41 @@
# Broker Mosquitto local — consommé par f2b-mqtt-action-banisher et par
# l'emetteur Django (Phase 2). Jamais exposé sur Internet : écoute en
# loopback, plus (optionnellement, cf. MQTT_VPN_LISTENER_IP dans .env) un
# listener additionnel sur le réseau VPN de confiance de CE noeud, pour
# permettre le développement/test de l'émetteur depuis un poste distant sur
# ce VPN. Cette IP est propre à chaque VPS (jamais une valeur partagée
# entre noeuds) — install.sh (deploy_mosquitto) remplace le marqueur
# ci-dessous à partir de .env, ou le retire entièrement si
# MQTT_VPN_LISTENER_IP est vide. NE PAS répéter ce marqueur ailleurs dans ce
# fichier, y compris en commentaire : la substitution est un remplacement
# global (gsub), toute autre occurrence littérale serait aussi affectée.
#
# Déployé par install.sh vers /etc/mosquitto/conf.d/mosquitto-local.conf
per_listener_settings true
# Avec per_listener_settings, allow_anonymous/password_file doivent être
# répétés sous chaque listener : ils ne s'appliquent qu'au listener sous
# lequel ils apparaissent, jamais rétroactivement aux précédents.
listener 1883 127.0.0.1
protocol mqtt
allow_anonymous false
password_file /etc/mosquitto/passwd_local
__VPN_LISTENER_BLOCK__
# Pas de persistance : les événements sont consommés en temps réel,
# aucun besoin de rejouer l'historique après un redémarrage du broker.
persistence false
# Pas de log_dest ici : /etc/mosquitto/mosquitto.conf (paquet Debian) en
# déclare déjà un ("log_dest file /var/log/mosquitto/mosquitto.log"),
# lu avant ce conf.d — en rajouter un second (même en syslog) fait
# planter mosquitto au chargement ("Duplicate 'log_dest file' value").
# C'est précisément ce fichier que surveille la jail fail2ban "mosquitto"
# (jail.d/mosquitto.conf) : si jamais un jour ce défaut du paquet
# disparaît/change, il faudra le redéclarer ici explicitement plutôt que
# supposer qu'il existe toujours.
log_type error
log_type warning
log_type notice
+33
View File
@@ -0,0 +1,33 @@
# Broker Mosquitto master (Phase 3) — écoute 8883, TLS obligatoire.
# Cohabite avec le listener local (mosquitto-local.conf) dans le même
# processus mosquitto : per_listener_settings s'applique par listener, pas
# de conflit entre les deux fichiers de conf.d.
#
# Deux chaînes de confiance distinctes :
# - certfile/keyfile : certificat Let's Encrypt du domaine __MQTT_MASTER_DOMAIN__
# (identité TLS présentée par le serveur) — copié par
# certbot-deploy-mosquitto.sh vers /etc/mosquitto/master-tls/, jamais lu
# directement dans /etc/letsencrypt/ (verrouillé root-only par certbot,
# illisible pour l'utilisateur système 'mosquitto')
# - cafile : CA privée dédiée (scripts/master-ca-init.sh), qui NE SERT QU'À
# vérifier les certificats clients des noeuds (mTLS) — Let's Encrypt ne
# signe pas de certificats clients.
#
# Déployé par install.sh (install-master) vers /etc/mosquitto/conf.d/mosquitto-master.conf
listener 8883
protocol mqtt
cafile /etc/mosquitto/master-ca/ca.crt
certfile /etc/mosquitto/master-tls/fullchain.pem
keyfile /etc/mosquitto/master-tls/privkey.pem
tls_version tlsv1.2
# mTLS : le certificat client est obligatoire, et son CN devient l'identité
# MQTT du noeud (exploité par master-acl.conf pour restreindre chaque noeud
# à ses propres topics).
require_certificate true
use_identity_as_username true
allow_anonymous false
acl_file /etc/mosquitto/master-acl.conf
@@ -0,0 +1,40 @@
# Renforcement du service mosquitto par défaut du paquet Debian.
# L'utilisateur dédié "mosquitto" est déjà créé par le paquet ; ce drop-in
# ne fait que restreindre ses droits système.
#
# Déployé par install.sh vers /etc/systemd/system/mosquitto.service.d/override.conf
[Service]
# User=/Group= explicites : sans ça, RuntimeDirectory= (ci-dessous) crée
# /run/mosquitto appartenant à root (propriétaire par défaut quand User=
# est vide), car ce chemin est bind-monté dans le namespace propre à
# ExecStart avec cette ownership-là — le `chown mosquitto:mosquitto
# /run/mosquitto` du ExecStartPre du paquet Debian n'a aucun effet
# persistant sur cette vue. Résultat observé : mosquitto démarre root,
# abandonne ses privilèges en interne vers l'utilisateur "mosquitto"
# (aucune directive "user" dans mosquitto.conf nécessaire, comportement
# par défaut du binaire), puis échoue avec "Unable to write pid file"
# (EACCES sur /run/mosquitto/mosquitto.pid, confirmé via strace). Fixer
# User=/Group= ici fait créer /run/mosquitto directement avec le bon
# propriétaire, sans dépendre du chown du ExecStartPre.
User=mosquitto
Group=mosquitto
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/mosquitto /var/log/mosquitto
# /run/mosquitto est du tmpfs, recréé à chaque démarrage : lister ce chemin
# dans ReadWritePaths= exige qu'il existe déjà AVANT que systemd ne monte le
# namespace de l'unité (donc avant même le premier ExecStartPre), ce que
# rien ne garantit après un redémarrage du VPS ("Failed to set up mount
# namespacing: /run/mosquitto: No such file or directory", 226/NAMESPACE).
# RuntimeDirectory= est le mécanisme systemd natif pour ce cas précis : il
# crée /run/mosquitto (proprio mosquitto, cf. User=/Group= ci-dessus) lui-
# même, avant tout Exec*, et le recrée à chaque démarrage — pas de
# dépendance sur un état laissé par un run précédent.
RuntimeDirectory=mosquitto
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6