Notre parcours de conception et développement vers le support du cluster Redis dans OpenCTI
Exceptionnellement, cet article ne portera pas sur une nouvelle fonctionnalité de threat intelligence d'OpenCTI, notre plateforme de threat intelligence open-source, mais sur notre expérience d'adaptation du produit afin de supporter un environnement Redis Cluster (à partir d'OpenCTI 5.7.X).
Au début du parcours d'OpenCTI, nous avons utilisé certaines fonctionnalités Redis qui ne sont malheureusement pas compatibles avec la version clusterisée. Cet article vise à expliquer ce que nous avons changé pour finalement implémenter le support du cluster Redis tout en conservant la compatibilité avec le déploiement mono-nœud.
Utilisation de Redis dans OpenCTI
Pour mieux comprendre le défi, commençons par expliquer pourquoi nous avons utilisé Redis en premier lieu dans le contexte d'OpenCTI. Comme décrit ci-dessous, la base de données Redis est une pièce centrale de la stack technologique de la plateforme.

Stack technique d'OpenCTI
Sessions partagées
Comme OpenCTI supporte le déploiement en cluster pour la plateforme, les sessions utilisateur sont stockées dans Redis. Nous avons choisi de stocker les sessions dans Redis et non dans ElasticSearch/OpenSearch car la base de données dispose de fonctionnalités comme l'expiration automatique qui est essentielle pour la gestion des sessions, mais aussi parce que stocker les sessions en mémoire permet de meilleures performances lors de la récupération de l'objet plusieurs fois dans un court laps de temps.
Gestion du cluster
La recommandation pour des performances optimales et une haute disponibilité est de déployer plusieurs instances de plateforme OpenCTI en cluster et de répartir les workers et les jobs entre ces instances. Chaque instance s'enregistre dans des clés spécifiques pour partager des informations en temps réel concernant :
- Le quorum, les versions et les signaux de vie.
- Les processus d'arrière-plan lancés et verrouillés.
- Toute autre information utile pour maintenir la cohérence du cluster.

Statut du cluster
Verrous
OpenCTI s'appuie sur ElasticSearch/OpenSearch pour stocker les connaissances (entités et relations). Nous avons fait ce choix car ce système de base de données offre la meilleure couverture de nos cas d'usage en termes de volume de données, de performances d'ingestion, de recherche plein texte et d'agrégation/corrélation. Cependant, cette base de données n'est pas transactionnelle. Pour éviter les problèmes de concurrence, nous avons implémenté un système de verrouillage rapide (grâce à Redlock) qui utilise Redis en sous-main. Le système de verrouillage a, par conception, un temps d'expiration court, donc Redis convient parfaitement pour ce type d'usage (même système que l'expiration automatique des sessions).

Flux de création avec verrous et résolution
Observabilité de l'ingestion
Dans OpenCTI, le mécanisme d'ingestion des différents connecteurs peut être surveillé directement dans l'interface utilisateur. Pour gérer ce cas d'usage, les connecteurs déclarent des « travaux » (généralement un bundle STIX) et chaque travail est constitué d'un certain nombre d'opérations (généralement des fragments de bundle STIX). Lorsqu'un worker a traité un fragment, le nombre d'opérations traitées est incrémenté.

Comptage des opérations au sein d'un travail
Cette surveillance permanente peut être intensive en E/S, donc Redis est utilisé pour maintenir le décompte de toutes les opérations traitées. À la fin du travail, les chiffres finaux sont stockés dans ElasticSearch/OpenSearch.
Publication-abonnement pour les utilisateurs et le cache
Redis est utilisé comme système pub/sub pour notifier les utilisateurs lorsque quelque chose change dans la plateforme (création, modification et suppression) mais aussi pour notifier le cache interne d'OpenCTI que certaines données doivent être rafraîchies (invalidation).
Stream(s)
Enfin, Redis est également utilisé pour stocker l'ensemble du flux d'informations de threat intelligence qui représente toutes les opérations de connaissance au sein de la plateforme (création, modification et suppression). Plusieurs fonctionnalités intégrées d'OpenCTI s'appuient sur ce flux pour prendre des décisions et réagir en temps réel (notifications, synchronisation, moteur de règles, etc.).

Utilisation du stream dans OpenCTI
D'où nous sommes partis et les limitations
Sur la base de toutes les fonctionnalités décrites ci-dessus, la conception initiale a été réalisée pour un déploiement Redis mono-nœud. Fondamentalement, pour supporter les cas d'usage d'OpenCTI, nous avons principalement utilisé 3 fonctionnalités importantes de la base de données Redis qui ont des impacts directs sur notre parcours vers le support du mode cluster.
Bases de données logiques multiples
Nous avons décidé d'utiliser la capacité de bases de données multiples dans Redis pour séparer les différents types de clés que nous devons stocker. Au début, il semblait être une bonne idée de séparer les préoccupations et de stocker les données principales dans la base de données 0 et le suivi/monitoring dans la base de données 1. En réalité, ce n'était pas un mauvais choix, mais cette fonctionnalité n'est pas disponible en mode cluster.
Scanning de clés par motif
Dans OpenCTI, certaines API (et écrans d'interface utilisateur) sont directement liées à des fonctions utilisées pour lister les clés stockées dans la base de données Redis. Par exemple, la liste des sessions dans la section des paramètres qui permet à un administrateur OpenCTI de les visualiser (et les terminer).
Pour ce faire, nous avons utilisé la commande scan pour obtenir toutes les clés commençant par session:*. Ce n'est pas une mauvaise façon de procéder, mais en mode cluster, il est nécessaire d'itérer sur tous les nœuds pour démarrer un scan pour chacun d'eux. Cela semble faisable, mais cela change beaucoup la logique du code source entre les modes mono-nœud et cluster.
Obtenir plusieurs clés en un seul appel
Redis offre la puissante commande mget qui permet de récupérer plusieurs clés dans la même requête. Cette fonctionnalité est très puissante, mais il n'est pas toujours possible de l'utiliser dans un cluster Redis. Cette commande ne peut être utilisée en cluster que si toutes les clés sont situées sur le même nœud. Pour y parvenir, il est généralement nécessaire d'établir une stratégie en fonction de l'usage pour colocaliser certaines clés en forçant le calcul de hachage.
Client Redis avec ioredis
Comme la plateforme OpenCTI est développée en NodeJS, nous avons décidé d'utiliser le client ioredis. Au début, nous utilisions simplement le constructeur de client par défaut qui n'est pas exactement le même lorsqu'il s'agit de l'utiliser contre un cluster Redis. Selon le Redis utilisé, un client différent peut être nécessaire. Un défi pour nous était également de trouver un moyen de supporter à la fois Redis mono-nœud et Redis cluster sans développer deux clients différents.
Qu'avons-nous fait ?
Pour supporter le cluster Redis, nous n'avons eu d'autre choix que de passer en revue toutes les limitations et de changer notre approche de conception et notre code. Passons en revue les différents usages et leurs impacts.
Client Redis
Côté client, nous avons décidé de trouver un moyen d'utiliser la même approche qu'il s'agisse d'un nœud unique ou d'un cluster. Pour cela, nous nous sommes concentrés sur l'utilisation de fonctionnalités disponibles dans les deux approches. Selon la configuration déclarée pour la plateforme, nous instancions le bon client et l'utilisons ensuite simplement sans code spécifique.
Observabilité, pub-sub et stream
Nous n'avons rien fait sur cette partie car l'implémentation actuelle était entièrement supportée par le cluster Redis.
Bases de données logiques multiples
Nous avons simplement supprimé l'utilisation de cette fonctionnalité. Tous les clients instanciés se connectent maintenant simplement à la base de données 0 car le partage des données est directement géré par le cluster.
Verrous
La gestion des verrous était un peu spécifique en raison de l'utilisation de la bibliothèque redlock. Cette bibliothèque exploite la commande mget et des scripts pour obtenir les verrous aussi rapidement que possible. C'est formidable, mais cela empêche malheureusement la possibilité de répartir les verrous à travers le cluster.
Parce que les verrous sont créés et récupérés très souvent dans un court laps de temps, c'est un cas d'usage où forcer la colocalisation a du sens. Ensuite, nous avons préfixé toutes les clés de verrou avec {lock} pour forcer Redis à colocaliser les clés et donc avoir la possibilité de continuer à utiliser la bibliothèque redlock sans problème.
Sessions et gestion du cluster
Nous avons décidé de changer la conception de la façon dont OpenCTI liste les clés pour éviter d'utiliser la commande scan. Par conséquent, nous avons créé une nouvelle approche utilisant un SET Redis pour maintenir la liste des clés liées aux mêmes besoins. Cette approche était possible car les sessions et les informations de cluster représentent une quantité limitée de données.
Avec cette nouvelle conception, il est maintenant possible de répartir toutes les clés pour les sessions et les informations de cluster, et d'utiliser le SET pour obtenir la liste des informations lorsque nécessaire. En raison des shards, toutes les clés dans le SET doivent être récupérées indépendamment avec une simple commande get.
Conclusion
Nous espérons que cet article vous a aidé à comprendre l'utilisation de Redis au sein de la plateforme OpenCTI. De plus, nous l'avons écrit pour fournir à tous les développeurs envisageant de passer à Redis Cluster des informations pertinentes et des leçons apprises. Si vous avez des connaissances sur Redis et souhaitez nous aider à mieux l'utiliser, n'hésitez pas à rejoindre le canal Slack de la communauté !
Lire la suite
Explorez des sujets et analyses associés
Votre SOC n'a pas de problème de données. Il a un problème de coordination.

Workflows d'approbation de l'intelligence : garantir la qualité des données et rationaliser les processus
