Concevoir un sensor avancé capable de surveiller et de disséquer les messages réseau n'a rien d'évident, surtout face à des services cloud modernes et complexes.
Chez Attribute™, nous sommes particulièrement fiers de notre Sensor. Il nous permet d'observer le trafic et d'obtenir des informations précieuses sur l'utilisation des ressources cloud et les flux applicatifs. C'est pourquoi nous investissons en continu dans le développement de ses capacités.
Dans cet article, nous partageons ce que nous avons appris en ajoutant de nouvelles capacités d'analyse pour prendre en charge Google Pub/Sub.
Le défi : analyser le protocole Google Pub/Sub
Le défi était clair : comment prendre en charge l'analyse des messages du service Google Cloud Pub/Sub, qui repose sur le protocole gRPC, tout en couvrant également d'autres services GCP et les communications gRPC en Python ?
Au cœur de ce défi se trouvait PubSub, le service de file de messages de Google, qui communique via gRPC, un protocole reposant sur HTTP/2 et sérialisant les messages en Protobuf. Il fallait comprendre comment capturer et décoder ces messages à un niveau granulaire, tout en composant avec des données chiffrées et compressées circulant sur le réseau.
Première étape : mettre en place l'environnement PubSub
Nous avons commencé par créer un environnement contrôlé dans Google Cloud pour générer des messages PubSub. Cela impliquait la configuration d'un topic PubSub, la création d'un canal d'abonnement et l'écriture de scripts Python pour simuler des événements réels de publication et d'abonnement.
Nous avons ensuite écrit un script simple côté publisher pour envoyer des messages dans le canal, ainsi qu'un second script jouant le rôle de serveur subscriber, en attente des messages provenant du canal d'abonnement créé.
Voici un extrait de la logique du script publisher :
from``google.cloud``import``pubsub_v1from``google.oauth2``import``service_accountdef``test_pubsub_v1_publisher_client():# Create a Pub/Sub publisher client with the credentials`<br>credentials=service_account.Credentials.from_service_account_file(<br>```TEST_CONFIG[``"service_account_json_file_path"``])`<br>publisher=pubsub_v1.PublisherClient(credentials=credentials)<br>```# Publish the messagemessage_data``=``"Hello, Pub/Sub!"`<br>future=publisher.publish(<br>```topic``=``TEST_CONFIG[``"topic_path"``], data``=``message_data.encode(``"utf-8"``))`<br>logger.info(f"Published message: {future.result()}")` |
Tout étant en place, l'objectif était que notre sensor capte les messages PubSub circulant sur le réseau. Pourtant, il n'a détecté ni trafic réseau, ni activité de la bibliothèque SSL. Ce premier échec nous a poussés à examiner la pile réseau plus en profondeur.
Aller plus loin : comprendre la pile réseau de PubSub
Après quelques recherches, nous avons découvert que Google PubSub communique via gRPC, qui utilise HTTP/2 comme couche de transport et Protobuf pour la sérialisation des messages. La bibliothèque `grpcio`, qui sous-tend gRPC en Python, ajoute une couche de complexité supplémentaire en intégrant des fragments d'OpenSSL dans ses propres modules Cython dédiés au chiffrement et à la sécurité du transport.
En traçant le fonctionnement interne de `grpcio`, nous avons fini par identifier son recours à un module d'extension C (`cygrpc.cpython-x86_64-linux-gnu.so`) chargé de traiter les messages gRPC chiffrés. Cette découverte a été décisive : nous tenions enfin une cible pour notre sensor, à savoir intercepter ces fonctions gRPC de bas niveau pour capturer les messages PubSub.
Le déclic : intercepter les messages gRPC
Le déclic est venu lorsque nous avons ciblé la Transport Security Interface (TSI) au sein de `grpcio`. Ce module gère le chiffrement et le déchiffrement des messages gRPC, et nous avons compris qu'en plaçant des sondes sur certaines fonctions TSI précises, nous pouvions capturer les données chiffrées au moment de leur traitement.
En installant des sondes eBPF (extended Berkeley Packet Filter) sur des fonctions TSI critiques comme `protect` et `unprotect`, nous avons pu intercepter les trames HTTP/2 brutes, autrement dit les messages gRPC encapsulés dans du Protobuf.
Voici la structure `tsi_frame_protector_vtable`, utilisée en interne dans la bibliothèque pour le chiffrement et le déchiffrement du texte en clair et du texte chiffré :
struct``tsi_frame_protector_vtable {tsi_result (*protect)(tsi_frame_protector* self,`<br>constunsignedchar* unprotected_bytes,`<br>```size_t* unprotected_bytes_size,<br>```unsigned``char``* protected_output_frames,size_t``* protected_output_frames_size);`<br>tsi_result (protect_flush)(tsi_frame_protector self,<br>```unsigned``char``* protected_output_frames,size_t``* protected_output_frames_size,`<br>size_t* still_pending_size);`<br>```tsi_result (*unprotect)(tsi_frame_protector* self,`<br>```constunsignedchar* protected_frames_bytes,<br>```size_t``* protected_frames_bytes_size,unsigned``char``* unprotected_bytes,`<br>size_t* unprotected_bytes_size);`<br>```void(destroy)(tsi_frame_protector self);<br>};` |
Décoder les messages PubSub
Une fois les trames HTTP/2 brutes capturées, restait à les décoder. Il fallait identifier les en-têtes gRPC, précédés d'un champ de longueur et d'un indicateur de compression, puis suivis du message encodé en Protobuf. Nous avons étendu notre décodeur HTTP/2 pour gérer cette structure.
Voici le processus en version simplifiée :
1. Intercepter le trafic gRPC : notre sonde eBPF capture les données en clair issues des fonctions TSI de gRPC.
2. Décoder les trames HTTP/2 : nous extrayons les en-têtes des messages gRPC ainsi que les messages Protobuf associés.
3. Analyser les données Protobuf : à l'aide du protocole PubSub, nous décodons des messages comme `PublishRequest` et `AcknowledgeRequest`.
Nous pouvions alors analyser les messages Google PubSub en temps réel, faisant de notre sensor un outil puissant pour surveiller le trafic réseau dans les environnements GCP.
Étendre la prise en charge à d'autres versions de gRPC
L'un de nos derniers obstacles : garantir la compatibilité avec plusieurs versions de la bibliothèque Python `grpcio`. Nous avons implémenté une logique capable de localiser et d'analyser dynamiquement les symboles utilisés dans les différentes versions de `grpcio`. Cela nous a conduits à plonger dans l'analyse binaire, en nous appuyant sur diverses méthodes : analyse des symboles de débogage DWARF, analyse binaire (via notamment le framework Zanalysis ou les pre-script et post-script Ghidra), rétro-ingénierie automatisée par script et extraction de symboles, afin d'automatiser l'identification des fonctions pertinentes dans chaque version de la bibliothèque.
La suite : au-delà de Python
Si notre priorité était Python, le framework gRPC est utilisé dans de nombreux langages de programmation, notamment C++, Java et Go. En réussissant à disséquer les messages gRPC en Python, nous avons posé les bases pour étendre cette prise en charge à d'autres langages avec un effort relativement modéré. Chaque nouveau langage nécessitera d'approfondir l'étude de son implémentation gRPC, mais les principes fondamentaux restent les mêmes.
Conclusion : un nouveau niveau de visibilité
Grâce à des recherches minutieuses, à de nombreux essais et à une compréhension approfondie de la pile Google PubSub et gRPC, nous avons pu bâtir un système robuste pour intercepter et disséquer les messages PubSub. Cette solution renforce non seulement la visibilité sur les services GCP, mais ouvre aussi la voie à une prise en charge élargie de gRPC dans différents langages et environnements.
Fort de ces capacités, nous sommes mieux armés pour surveiller, diagnostiquer et optimiser les performances des services cloud reposant sur gRPC et PubSub. Et à mesure que nous étendons la prise en charge à de nouveaux services, les possibilités offertes par ce sensor ne cessent de croître.
Nous espérons que cet article vous accompagnera dans votre exploration de l'analyse des protocoles basés sur gRPC. Pour aller plus loin, n'hésitez pas à nous contacter !
Quelques lectures complémentaires recommandées :