Asegurando comunicación entre microservicios con HMAC

Asegurando comunicación entre microservicios con HMAC

En las arquitecturas basadas en microservicios, uno de los desafíos más importantes es asegurar que las comunicaciones internas entre servicios sean seguras. Aunque existen soluciones como proxies y mallas de servicios (service meshes) que ayudan a controlar y asegurar las comunicaciones, muchas veces es necesario añadir capas adicionales de seguridad sin depender completamente de herramientas externas.

Un mecanismo efectivo para reforzar la seguridad en la comunicación entre microservicios es utilizar firmas criptográficas como HMAC-SHA256. Este mecanismo asegura que cada petición que viaja entre microservicios esté validada por el remitente y pueda ser autenticada por el destinatario, previniendo que actores externos interfieran en estas comunicaciones.

En este artículo, exploraremos cómo implementar un esquema de validación basado en HMAC-SHA256, utilizando Python como ejemplo de desarrollo. Veremos cómo se genera la firma, cómo se validan las solicitudes y qué consideraciones debemos tener al implementar esta solución.

¿Qué es HMAC-SHA256?

HMAC (Hash-based Message Authentication Code) es un algoritmo que combina un hash criptográfico con una clave secreta para asegurar tanto la integridad como la autenticidad de un mensaje. En el caso de HMAC-SHA256, se utiliza el algoritmo de hash SHA-256, que es altamente seguro y ampliamente utilizado.

El proceso de generación de un HMAC se basa en lo siguiente:

  1. Un mensaje (en nuestro caso, una petición HTTP) y una key (clave secreta) son tomados como entrada.
  2. El algoritmo genera una firma única a partir de estos datos.
  3. Esta firma es enviada junto con el mensaje para que el receptor pueda validar que el mensaje no ha sido alterado y que proviene de una fuente autorizada.

Implementación del esquema HMAC-SHA256 en microservicios

Como prueba de concepto implementamos un sistema en el que cada petición HTTP entre microservicios lleva consigo un timestamp y una firma HMAC generada a partir de una clave secreta. Esto asegura que cada solicitud pueda ser autenticada por el microservicio destinatario, también sirve como base para prevenir replay-attacks ya que se pueden ignorar peticiones con una duración superior a lo esperado.

Veamos los pasos básicos de este esquema:

  1. Generación de la firma: Cada vez que un microservicio realiza una petición a otro, genera un HMAC utilizando un timestamp, el body y una clave secreta compartida.
  2. Inclusión de los headers: Los valores generados (timestamp y firma) se incluyen en los headers de la solicitud, utilizando las claves:
    1. x-hmac-timestamp: Contiene el timestamp de la petición.
    2. x-hmac-sha256: Contiene la firma HMAC.
  3. Validación en el receptor: El microservicio destinatario toma la petición, extrae los headers, y recalcula la firma utilizando su propia clave secreta y la combinación del timestamp con el body. Si la firma coincide, se autoriza la petición; si no, se rechaza.

Ejemplo de implementación en Python

Creamos un ejemplo práctico utilizando Python. Implementaremos tanto la generación como la validación de la firma HMAC-SHA256.

Paso 1: Dependencias y configuración
Necesitamos una instalación de Python 3 con las librerías Flask y requests, estas últimas podemos instalar con este comando:

pip3 install Flask requests

Paso 2: Crear el servidor (Flask)

Este será el servidor que recibirá las peticiones HMAC firmadas y validará la firma.

Crea un archivo llamado server.py:

from flask import Flask, request, jsonify
import hmac
import hashlib
import time

app = Flask(__name__)

SECRET_KEY = b'clave-secreta'

def verify_hmac_signature(request_body, timestamp, signature):
    # Crear la firma esperada usando el body y el timestamp
    message = f'{request_body}{timestamp}'.encode('utf-8')
    expected_signature = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()

    # Comparar la firma recibida con la esperada
    return hmac.compare_digest(expected_signature, signature)

@app.route('/api', methods=['POST'])
def api():
    # Obtener el body, timestamp y la firma de la petición
    request_body = request.data.decode('utf-8')
    timestamp = request.headers.get('x-hmac-timestamp')
    signature = request.headers.get('x-hmac-sha256')

    # Verificar si la firma es válida
    if not timestamp or not signature:
        return jsonify({"error": "Faltan headers HMAC"}), 400

    if verify_hmac_signature(request_body, timestamp, signature):
        return jsonify({"message": "Firma válida. Petición aceptada."})
    else:
        return jsonify({"error": "Firma inválida. Petición rechazada."}), 403

if __name__ == '__main__':
    app.run(port=8080)

Explicación del código:

  1. SECRET_KEY: La clave secreta que se usa para generar y verificar las firmas.
  2. verify_hmac_signature: Esta función toma el cuerpo de la petición, el timestamp y la firma, y genera la firma esperada. Luego compara esa firma con la firma recibida.
  3. API endpoint /api: Este es el endpoint donde el cliente enviará sus peticiones con HMAC. Valida la firma antes de aceptar o rechazar la petición.

Paso 3: Implementación del cliente

Este será el cliente que firmará las peticiones y las enviará al servidor.

Crea un archivo llamado client.py:

import requests
import hmac
import hashlib
import time

SECRET_KEY = b'clave-secreta'

def generate_hmac_signature(request_body, timestamp):
    message = f'{request_body}{timestamp}'.encode('utf-8')
    signature = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()
    return signature

def send_request():
    url = 'http://localhost:8080/api'
    request_body = '{"message":"Hola, microservicio"}'
    
    # Generar el timestamp
    timestamp = str(int(time.time()))
    
    # Generar la firma HMAC
    signature = generate_hmac_signature(request_body, timestamp)
    
    # Crear los headers con la firma
    headers = {
        'x-hmac-timestamp': timestamp,
        'x-hmac-sha256': signature
    }
    
    # Enviar la solicitud POST
    response = requests.post(url, data=request_body, headers=headers)
    
    print("Respuesta del servidor:", response.json())

if __name__ == '__main__':
    send_request()

Explicación del código:

  1. generate_hmac_signature: Genera la firma HMAC usando el cuerpo de la petición y el timestamp.
  2. send_request: Envía una solicitud HTTP POST al servidor con los headers x-hmac-timestamp y x-hmac-sha256, que contienen el timestamp y la firma respectivamente.

Paso 4: Ejecutar el servidor y cliente

Servidor:

En una terminal, ejecuta el servidor Flask con el siguiente comando:

python3 server.py

Cliente:

En otra terminal, ejecuta el cliente:

python3 client.py

El cliente enviará una petición al servidor y recibirás una respuesta. Si la firma es válida, verás algo como esto en la salida del cliente:

Respuesta del servidor: {'message': 'Firma válida. Petición aceptada.'}

Y en la terminal del servidor veremos el log de que ha procesado la petición correctamente.

Conclusión

Ahora que las arquitecturas basadas en microservicios ya no son una novedad, garantizar la seguridad de las comunicaciones internas se vuelve crucial. Implementar un esquema de validación basado en HMAC-SHA256 proporciona una capa adicional de seguridad que asegura tanto la integridad como la autenticidad de las peticiones entre servicios.

Este enfoque es sencillo de implementar, pero muy efectivo en mitigar riesgos en casos de man-in-the-middle, y con la ventaja de ser independiente a la infraestructura. Sin embargo, como con cualquier medida de seguridad, es importante revisar y actualizar regularmente las claves secretas utilizadas.

Read more