Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Análisis de mensajes gRPC y Google PubSub en Attribute™ Sensor: un recorrido por el stack de red

By Hili ParyentiOct 9, 20245 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

Desarrollar un sensor avanzado para monitorear y analizar mensajes de red no es tarea sencilla, sobre todo cuando hablamos de servicios cloud modernos y complejos.

En Attribute™ nos enorgullece particularmente nuestro Sensor. Nos permite observar el tráfico y obtener insights sobre el uso de recursos cloud y los flujos aplicativos, por eso invertimos y evolucionamos constantemente las capacidades del Sensor de Attribute.

En este post te contamos lo que aprendimos al sumar nuevas capacidades de análisis para dar soporte a Google Pub/Sub.

El desafío: analizar el protocolo de Google Pub/Sub

El desafío era claro: ¿cómo sumar soporte para analizar mensajes del servicio Google Cloud Pub/Sub, que corre sobre el protocolo gRPC, y a la vez cubrir otros servicios de GCP y comunicaciones gRPC basadas en Python?

En el centro del problema estaba PubSub, el servicio de cola de mensajes de Google, que se comunica mediante gRPC, un protocolo basado en HTTP/2 que serializa los mensajes en Protobuf. Necesitábamos entender cómo capturar y decodificar estos mensajes a nivel granular, todo mientras lidiábamos con datos cifrados y comprimidos que viajaban por la red.

Primer paso: preparar el entorno de PubSub

Empezamos por armar un entorno controlado en Google Cloud para generar mensajes de PubSub. Esto implicó crear un topic de PubSub, un canal de suscripción y scripts en Python para simular eventos reales de publicación y suscripción.

Después escribimos un script publisher simple para enviar mensajes al canal y otro script que actúa como servidor subscriber a la espera de mensajes del canal de suscripción creado.

A continuación, una sección parcial de la lógica del script publisher:

from``google.cloud``import``pubsub_v1
from``google.oauth2``import``service_account
def``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 message
message_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()}")`

Con todo listo, la idea era que nuestro sensor detectara los mensajes de PubSub a medida que viajaban por la red. Sin embargo, el sensor no captó tráfico de red ni actividad de la librería SSL. Este primer fracaso nos llevó a investigar el stack de red con más detalle.

Yendo más a fondo: entender el stack de red de PubSub

Tras investigar un poco, descubrimos que Google PubSub se comunica mediante gRPC, que usa HTTP/2 como capa de transporte y Protobuf para serializar los mensajes. La librería `grpcio`, que soporta gRPC en Python, suma complejidad al integrar partes de OpenSSL dentro de sus propios módulos personalizados en Cython para el cifrado y la seguridad del transporte.

Rastreamos el funcionamiento interno de la librería `grpcio` y terminamos identificando el uso de un módulo C-Extension (`cygrpc.cpython-x86_64-linux-gnu.so`) responsable de manejar los mensajes gRPC cifrados. Este hallazgo fue clave: ya teníamos un objetivo para nuestro sensor, interceptar estas funciones gRPC de bajo nivel para capturar los mensajes de PubSub.

El avance: interceptar mensajes gRPC

El avance llegó cuando pusimos el foco en la Transport Security Interface (TSI) dentro de `grpcio`. Este módulo se encarga del cifrado y descifrado de los mensajes gRPC, y nos dimos cuenta de que, colocando probes en funciones TSI específicas, podíamos capturar los datos cifrados a medida que se procesaban.

Al instalar probes de eBPF (extended Berkeley Packet Filter) en funciones TSI críticas como `protect` y `unprotect`, logramos interceptar frames HTTP/2 en crudo, es decir, los mensajes gRPC envueltos en Protobuf.

Este es el struct "tsi_frame_protector_vtable", que se usa internamente en la librería para el cifrado / descifrado de plaintext / 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>};`

Decodificar los mensajes de PubSub

Una vez capturados los frames HTTP/2 en crudo, el siguiente paso fue decodificarlos. Esto implicó identificar los headers de gRPC, que llevan como prefijo un campo de longitud y un flag de compresión, seguidos del mensaje codificado en Protobuf. Extendimos nuestro decodificador HTTP/2 para soportarlo.

Este es un desglose simplificado del proceso:

1. Interceptar el tráfico gRPC: nuestro probe eBPF captura los datos en plaintext desde las funciones TSI de gRPC.

2. Decodificar los frames HTTP/2: extraemos los headers de los mensajes gRPC y sus mensajes Protobuf asociados.

3. Parsear los datos Protobuf: con el protocolo de PubSub, decodificamos mensajes como `PublishRequest` y `AcknowledgeRequest`.

Esto nos permitió analizar mensajes de Google PubSub en tiempo real, convirtiendo a nuestro sensor en una herramienta potente para monitorear el tráfico de red en entornos GCP.

Ampliar el soporte a otras versiones de gRPC

Uno de los últimos obstáculos fue garantizar la compatibilidad con múltiples versiones de la librería `grpcio` de Python. Implementamos una lógica para localizar y analizar dinámicamente los símbolos utilizados en las distintas versiones de `grpcio`. Esto nos obligó a meternos de lleno en el análisis binario, donde aprovechamos métodos de parseo de símbolos de debug desde DWARF, análisis binario (usando, entre otros, el framework Zanalysis y pre-script, post-script de Ghidra, etc.), ingeniería inversa automatizada mediante scripting y extracción de símbolos, para automatizar el proceso de identificación de las funciones relevantes en cada versión de la librería.

El futuro: escalar más allá de Python

Si bien el foco principal fue Python, el framework gRPC se usa en muchos lenguajes de programación, entre ellos C++, Java y Go. Al haber logrado analizar mensajes gRPC basados en Python, ya sentamos las bases para llevar este soporte a otros lenguajes con un esfuerzo relativamente bajo. Cada nuevo lenguaje requerirá investigar su implementación de gRPC, pero los principios de fondo no cambian.

Conclusión: un nuevo nivel de visibilidad

A partir de una investigación minuciosa, mucho ensayo y error, y un conocimiento profundo del stack de Google PubSub y gRPC, logramos construir un sistema robusto para interceptar y analizar mensajes de PubSub. Esta solución no solo mejora la visibilidad sobre los servicios de GCP, sino que también prepara el terreno para un soporte más amplio de gRPC en distintos lenguajes y entornos.

Con estas capacidades en marcha, estamos mejor preparados para monitorear, diagnosticar y optimizar el rendimiento de los servicios cloud que usan gRPC y PubSub. Y a medida que seguimos ampliando el soporte a más servicios, las posibilidades de este sensor no dejan de crecer.

Esperamos que este blog te sirva en tu camino de análisis de protocolos basados en gRPC. Si buscas más información, ¡escríbenos sin dudarlo!

Y aquí van algunas lecturas recomendadas para seguir profundizando:

GRPC Core Concepts GRPC Stacks

Interceptors Guide