reaction v2 : Rust & NLnet
This article is also available in 🇬🇧 english.
Pour une introduction à reaction, voir l'article précédent à ce sujet
J'ai le plaisir de vous annoncer la publication de reaction v2.0.0 !
Cette nouvelle version apporte peu de nouvelles fonctionnalités, mais ouvre la voie pour un futur radieux à reaction 🌞
TL;DR🔗
Au programme de cet article :
- Réécriture en Rust
- Benchmark
- Incompatibilités de la v1 à la v2
- Nouvelles fonctionnalités
- Fonctionnalités à venir
- Support de NLnet
🦀 Réécriture en Rust🔗
J'ai entamé il y a un an une réécriture complète de reaction en Rust, avec pour objectifs :
- Une meilleure performance et une meilleure robustesse
- Un langage plus en vogue, pour attirer les contributions et profiter d'un meilleur écosystème de librairies
- Un langage plus adapté à la création d'un système de plugins performant (voir plus bas)
- Écrire des tests pour la majorité de la codebase.
Après de nombreux rebondissements, j'ai enfin une réimplémentation de reaction qui me satisfait pleinement. La première version fonctionnelle était prête en octobre, mais cette réécriture a été l'occasion pour moi de continuer de tester des systèmes internes différents.
J'ai notamment passé les mois de février et de mai à tester plusieurs systèmes de base de données, qui ne me satisfaisaient pas, jusqu'à réécrire ma propre implémentation. Il y a maintenant un mini-redis embarqué dans reaction !
📊 Benchmark🔗
Puisqu'on aime toustes la data, voici le résultat d'un benchmark.
Ce benchmark n'est pas du tout réaliste, il envoie 4 flux d'un million de logs chacun le plus rapidement possible, demandant à reaction de traiter toutes ces lignes, et de réaliser environ 6000 actions (un
sleep 0.1
). Il permet de donner une idée de comment réagit reaction à un stress énorme.Bien sûr, tous les tests sont à prendre avec un grain de sel, car ils ne permettent pas de tester la vie réelle.
Les statistiques sont produites par systemd.
Version Go, v1.4.1
:
Service runtime: 2min 10.024s
CPU time consumed: 6min 47.643s
Memory peak: 13.6G (swap: 6.6G)
IO bytes read: 18.5G written: 21.3G
Version Rust, v2.0.0
:
Service runtime: 42.235s
CPU time consumed: 4min 27.828s
Memory peak: 4.3G (swap: 0B)
IO bytes written: 499.9M
Ces tests ont été effectués sur un PC avec un SSD NVME tout neuf et 16G de RAM. Les différences en écriture disque auraient un plus gros impact sur un disque moins performant. Les différence en temps total auraient sûrement été moins importantes si reaction Go n'avait pas eu à swapper.
Il faut noter que les différences de performance ne sont pas imputables qu'au langage de programmation utilisé, mais aussi voire surtout aux choix techniques qui sont faits pour la communication entre les composants du système, la base de données (même structure générale mais avec des différences d'implémentation), etc.
Pour preuve, la première version de la réécriture, calquée sur le modèle de goroutines et channels proposé par Go, et moins adapté à Rust, avait des performances bien moins bonnes que la version Go.
⚙️ Incompatibilités🔗
Les différences de comportement entre reaction v1 et reaction v2 sont peu nombreuses.
Ces changements peuvent créer des effets de bord dans certains cas :
🧑🏫 Évaluation de la configuration🔗
Le chargement de la configuration est plus strict sur reaction v2, qui détecte notamment les champs mal nommés, auparavant ignorés.
En cas de configuration incorrecte, une erreur intelligible sera levée au lancement.
Une nouvelle commande, reaction test-config
, permet même de tester la configuration en amont.
📏 Moteur de regex🔗
Les librairies Go et Rust pour les regex comportent quelques différences de syntaxe pour des fonctions avancées.
⛲ Streams🔗
Auparavant, reaction ne lisait que la sortie stdout des commandes lancées.
Maintenant, les sorties stdout et stderr sont lues.
📂 Base de données🔗
Les formats de base de données de la v1 et la v2 sont incompatibles.
Les données stockées (matches, actions en cours) seront perdues.
Dans le state directory de reaction (probablement /var/lib/reaction
), les fichiers suivants peuvent être supprimés :
reaction-matches.db
,reaction-flushes.db
(v1)- le dossier
data
(v2.0.0-rc2)
La v2 utilise un fichier unique appelé reaction.db
, au format JSONL (un objet JSON par ligne)
🎉 Nouvelles fonctionnalités🔗
La réécriture était l'objectif principal, mais quelques ajouts se sont glissés dans cette nouvelle version !
Dossier de configuration🔗
Principal ajout réel pour les admins, reaction supporte à présent une configuration répartie en plusieurs fichiers. Bien pratique quand on génère de la configuration avec des outils d'Infra as Code, ou quand on aime mettre les petits plats dans les grands.
Cette fonctionnalité est documentée dans la FAQ du wiki
🐚 Ligne de commande🔗
L'interface en ligne de commande est la même, mais plus flexible :
- L'ordre des arguments est moins strict,
- Chaque sous-commande a son propre
--help
.
🐚🐚 Complétions shell & Man pages🔗
Des complétions shell sont maintenant disponibles pour bash, zsh et fish.
Des pages de manuel pour la CLI légèrement plus fournies que l'aide de --help
, sont également crées.
📦 Packaging🔗
reaction est maintenant précompilé pour 2 architectures: amd64 (x86-64) et arm64.
Pour chaque architecture, une archive .tar
et un paquet debian sont disponibles.
Chacun d'entre eux contient :
- Les binaires :
reaction
,ip46tables
etnft46
- Les pages de manuel et les complétions shell
- Un fichier de service systemd
De plus, le .tar contient un Makefile pour installer les fichiers précédents.
🔮 Fonctionnalités à venir🔗
Ajout de fonctionnalités manquantes🔗
Maintenant que les fondations sont posées, je vais enfin ajouter de nouvelles fonctionnalités, pour la plupart demandées par des utilisateurs et utilisatrices de reaction :
- Meilleur support des adresses IP (#79)
- Gestion paramétrable des duplicatas (#68)
- Actions "one shot" (pour l'alerting notamment) (#92)
- Commande de ban (#93)
- Support plus complet pour la distribution NixOS (ajout d'un module)
Documentation🔗
En parallèle, je compte améliorer la documentation, en écrivant :
- une référence complète des options de configuration
- un guide pas à pas pour la configuration d'un nouveau filtre
- plus d'exemples de config toute prête pour des services courants
Système de plugins🔗
Une fois ces améliorations faites, je vais me lancer dans un second gros chantier : ajouter un système de plugins.
Ce système permettra à d'autres personnes d'étendre reaction, sans toucher au cœur du code.
Il est déjà possible de faire à peu près ce qu'on veut avec reaction, en utilisant des scripts custom comme commande d'un stream ou d'une action. Mais ça peut vite être inefficace ou dégueu pour des choses compliquées, ou pour se connecter à d'autres systèmes.
Par exemple, si on veut entrer des IPs dans PostgreSQL, on peut créer une action comme ça :
{
insert_psql: {
cmd: ['psql', '-h', 'HOST', '-U', 'user', '-c', 'INSERT INTO ips (ip) VALUES(<ip>)'],
}
}
Ça fonctionne, mais ce n'est pas optimisé. Cet exemple va :
- Lancer un client PostgreSQL
- Qui va ouvrir une session avec le serveur
- S'authentifier
- Insérer une ligne (⚠️ sensible aux injections SQL dans ce cas minimal : voir le wiki pour une version sécurisée)
- Fermer la session.
Avoir un plugin PostgreSQL permettrait de garder une session ouverte avec le serveur, et de n'avoir qu'à insérer la ligne.
Exemple (fictif) :
{
insert_psql: {
plugin: 'psql',
options: {
host: 'HOST',
user: 'user',
query: 'INSERT INTO ips (ip) VALUES(%1)',
params: {
'%1': '<ip>'
}
}
}
}
Je n'ai pas prévu d'écrire moi-même des plugins pour des bases de données cependant ! Ce travail est laissé à d'autres, mais ça devrait être simple.
JSON🔗
De nombreux programmes permettent de loguer des données structurées, que ce soit en JSON,
ou en utilisant le format de journald, (que journalctl
peut ensuite afficher en JSON).
C'est possible de faire des regex sur du JSON, mais c'est désagréable et source potentielle d'erreurs.
Un plugin JSON pourrait remplacer les regex d'un filter reaction par des méthodes plus avancées.
Clustering🔗
Pour l'instant reaction fonctionne en solo.
Mais ce serait super qu'il puisse fonctionner en bande, pour que plusieurs instances de reaction, sur des machines différentes, puissent s'échanger des messages, comme des IPs à bannir.
Un plugin de clustering permettra ça.
Je pense que ce sera le plugin qui demandera le plus de travail ^^
HTTP soft ban🔗
En cas d'attaque, la réponse actuelle de reaction est de bannir l'IP complètement au niveau du firewall. C'est efficace, mais ça peut être source d'incompréhension en cas de faux positif, quand une personne légitime se retrouve bannie.
Un plugin qui crée un middleware HTTP, c'est à dire un serveur HTTP qui se place entre le reverse proxy et le serveur applicatif, pourrait corriger ce problème :
- Il serait complètement transparent pour les IPs légitimes, et passerait le trafic entre le reverse proxy et le serveur applicatif.
- Il afficherait un message user-friendly aux IPs bloquées, bloquant le trafic du reverse proxy vers le serveur applicatif.
J'ai envie de faire ça depuis un bon moment, et plusieurs personnes se sont montrées dubitatives quand à ce choix d'implémentation. C'est cependant le choix qui a été récemment fait pour Anubis, un logiciel de protection contre les bots qui peut être utilisé de concert avec reaction !
Firewall🔗
Les commandes d'iptables
et nftables
sont peu efficaces quand de nombreuses sont lancées en parallèle.
Ça peut causer une pression supplémentaire sur le serveur en cas d'attaque.
Un plugin qui maintiendrait une seule commande capable de streamer les actions de firewall à réaliser réduirait de beaucoup cette charge.
💰 NLnet🔗
Dans le but de pouvoir dédier plus de temps à reaction, j'ai fait une demande de bourse à NLnet en juin 2024. J'ai reçu la validation de cette bourse en janvier !
Toutes les fonctionnalités à venir décrites dans cet article seront financées par la fondation, me permettant de m'y consacrer pleinement tout en payant mon loyer 😄
C'est une super nouvelle 🎉
En bonus, cette bourse inclut un audit de sécurité, ce qui est spécialement important pour un logiciel comme celui-ci.
👉 La page du projet Reaction @ NLnet
🤝 Comment participer ?🔗
Utilisez reaction 😄
En cas de problème, ou d'idées, plusieurs espaces sont ouverts pour en discuter :
-
Les rooms Matrix de développement :
-
Les rooms Matrix de support :
-
Ping @ppom@mamot.fr sur le Fediverse
Et vos configurations sont plus que bienvenues sur le wiki ! N'hésitez pas à proposer une merge request (ou même à la poser dans une issue si flemme) sur le dépôt Git.
🫶 Remerciements🔗
Merci à @bcareil qui a écrit la fonctionnalité du dossier de configuration et corrigé d'autres problèmes dans l'implémentation Rust !
Merci à M pour son aide et ses conseils avisés dans les fonctionnalités avancées de Rust (cé compliqué).
Merci à Bertille pour son écoute et son avis sur les choix techniques et la gestion de projet.
Merci aux Rustacéens de #reaction-dev-fr:club1.fr pour leurs remarques judicieuses sur les choix de base de données.
Merci à Luc pour l'aide au packaging Debian.
Merci aux gens qui ont donnée des idées et alerté sur des problèmes dans les issues.