Sviluppare un sensore avanzato in grado di monitorare e analizzare i messaggi di rete non è affatto banale, soprattutto quando ci si confronta con servizi cloud moderni e complessi.
In Attribute™ siamo particolarmente orgogliosi del nostro Sensor: ci consente di osservare il traffico e ottenere insight sull'utilizzo delle risorse cloud e sui flussi applicativi. Per questo investiamo continuamente nello sviluppo delle sue funzionalità.
In questo articolo raccontiamo cosa abbiamo imparato aggiungendo nuove funzionalità di analisi per il supporto di Google Pub/Sub.
La sfida: analizzare il protocollo Google Pub/Sub
La sfida era chiara: come aggiungere il supporto per l'analisi dei messaggi provenienti dal servizio Google Cloud Pub/Sub, che si basa sul protocollo gRPC, coprendo al contempo anche altri servizi GCP e le comunicazioni gRPC in Python?
Al centro della sfida c'era PubSub, il servizio di messaging queue di Google, che comunica tramite gRPC, un protocollo basato su HTTP/2 che serializza i messaggi in Protobuf. Dovevamo capire come intercettare e decodificare questi messaggi a livello granulare, gestendo al contempo dati cifrati e compressi in transito sulla rete.
Il primo passo: preparare l'ambiente PubSub
Abbiamo iniziato allestendo un ambiente controllato in Google Cloud per generare messaggi PubSub. Abbiamo creato un topic PubSub, un canale di sottoscrizione e alcuni script Python per simulare eventi reali di pubblicazione e sottoscrizione.
Abbiamo poi scritto un semplice script publisher per inviare messaggi nel canale e un secondo script che funge da server subscriber, in attesa dei messaggi provenienti dal canale di sottoscrizione creato.
Ecco una parte della logica dello 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()}")` |
Una volta pronto tutto, l'obiettivo era che il nostro sensore intercettasse i messaggi PubSub in transito sulla rete. Il sensore, però, non rilevava alcun traffico di rete né attività della libreria SSL. Questo primo fallimento ci ha spinti ad analizzare più a fondo lo stack di rete.
Scavare più a fondo: capire lo stack di rete di PubSub
Dopo alcune ricerche, abbiamo scoperto che Google PubSub comunica tramite gRPC, che utilizza HTTP/2 come livello di trasporto e Protobuf per la serializzazione dei messaggi. La libreria `grpcio`, che sta alla base di gRPC in Python, aggiunge ulteriore complessità integrando parti di OpenSSL all'interno di moduli Cython personalizzati per la cifratura e la sicurezza del trasporto.
Abbiamo ricostruito il funzionamento interno della libreria `grpcio` e individuato l'uso di un modulo C-Extension (`cygrpc.cpython-x86_64-linux-gnu.so`) responsabile della gestione dei messaggi gRPC cifrati. Una scoperta decisiva: avevamo finalmente un bersaglio per il nostro sensore, ovvero intercettare queste funzioni gRPC di basso livello per catturare i messaggi PubSub.
La svolta: intercettare i messaggi gRPC
La svolta è arrivata quando ci siamo concentrati sulla Transport Security Interface (TSI) all'interno di `grpcio`. Questo modulo gestisce la cifratura e la decifratura dei messaggi gRPC e ci siamo resi conto che, posizionando probe su specifiche funzioni TSI, potevamo catturare i dati cifrati mentre venivano elaborati.
Installando probe eBPF (extended Berkeley Packet Filter) su funzioni TSI critiche come `protect` e `unprotect`, siamo riusciti a intercettare i frame HTTP/2 grezzi, ossia i messaggi gRPC incapsulati in Protobuf.
Di seguito la struct "tsi_frame_protector_vtable", utilizzata internamente dalla libreria per la cifratura/decifratura di plaintext e ciphertext:
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>};` |
Decodificare i messaggi PubSub
Una volta catturati i frame HTTP/2 grezzi, il passo successivo era decodificarli. Bisognava identificare gli header gRPC, preceduti da un campo di lunghezza e da un flag di compressione, seguiti dal messaggio codificato in Protobuf. Abbiamo esteso il nostro decoder HTTP/2 per gestire questo scenario.
Ecco una sintesi semplificata del processo:
1. Intercettazione del traffico gRPC: la nostra probe eBPF cattura i dati in chiaro dalle funzioni TSI di gRPC.
2. Decodifica dei frame HTTP/2: estraiamo gli header dei messaggi gRPC e i relativi messaggi Protobuf.
3. Parsing dei dati Protobuf: utilizzando il protocollo PubSub, decodifichiamo messaggi come `PublishRequest` e `AcknowledgeRequest`.
In questo modo abbiamo potuto analizzare i messaggi Google PubSub in tempo reale, trasformando il nostro sensore in uno strumento potente per il monitoraggio del traffico di rete negli ambienti GCP.
Estendere il supporto ad altre versioni di gRPC
Uno degli ultimi ostacoli è stato garantire la compatibilità con più versioni della libreria Python `grpcio`. Abbiamo implementato una logica per individuare e analizzare dinamicamente i simboli utilizzati nelle diverse release di `grpcio`. Ci siamo dovuti addentrare nell'analisi binaria, sfruttando tecniche come il parsing dei simboli di debug da DWARF, l'analisi binaria (con il framework Zanalysis e con pre-script/post-script di Ghidra, tra gli altri), il reverse engineering automatizzato tramite scripting e l'estrazione dei simboli per identificare in automatico le funzioni rilevanti in ciascuna versione della libreria.
Il futuro: oltre Python
Sebbene il nostro focus iniziale sia stato su Python, il framework gRPC è utilizzato in molti linguaggi di programmazione, tra cui C++, Java e Go. Essendo riusciti ad analizzare con successo i messaggi gRPC basati su Python, abbiamo gettato le basi per estendere questo supporto ad altri linguaggi con uno sforzo relativamente contenuto. Ogni nuovo linguaggio richiederà ulteriori ricerche sulla sua implementazione di gRPC, ma i principi di fondo restano gli stessi.
Conclusione: un nuovo livello di visibilità
Grazie a ricerca approfondita, sperimentazione e una conoscenza dettagliata dello stack Google PubSub e gRPC, siamo riusciti a costruire un sistema solido per intercettare e analizzare i messaggi PubSub. Questa soluzione non solo aumenta la visibilità sui servizi GCP, ma pone anche le basi per un supporto gRPC più ampio in linguaggi e ambienti diversi.
Con queste funzionalità a disposizione siamo in una posizione migliore per monitorare, fare troubleshooting e ottimizzare le prestazioni dei servizi cloud basati su gRPC e PubSub. E man mano che estendiamo il supporto ad altri servizi, le possibilità di questo sensore continuano a crescere.
Ci auguriamo che questo articolo possa esservi utile nel vostro percorso di analisi dei protocolli basati su gRPC. Se avete bisogno di ulteriori informazioni, non esitate a contattarci!
Di seguito alcune letture consigliate di approfondimento: