DEV 20 min

Apache Kafka en Spring Boot: Arquitectura, mTLS y ACLs

Domina Apache Kafka: Brokers, Topics, Partitions, Offsets y el adiós a Zookeeper con KRaft. Incluye una implementación completa de Productores y Consumidores usando Spring Boot y Docker Compose.

Escrito por Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

Arquitecturas Dirigidas por Eventos (EDA) y el Rol de Apache Kafka

En el diseño de sistemas distribuidos y microservicios modernos, la comunicación sincrónica tradicional mediante peticiones REST/HTTP introduce acoplamiento temporal y fragilidad: si un servicio aguas abajo experimenta degradación o una caída de red, las peticiones entrantes se acumulan, agotando los hilos del servidor y provocando fallos en cascada en toda la plataforma, como analizamos en nuestra guía sobre arquitectura de microservicios corporativa.

La *Arquitectura Dirigida por Eventos (Event-Driven Architecture - EDA) resuelve este problema desacoplando a los emisores (Producers) de los receptores (Consumers*) a través de un bus de eventos persistente. En este ámbito, Apache Kafka se ha consolidado como el estándar de facto de la industria.

A diferencia de las colas de mensajes tradicionales basadas en estándares JMS o AMQP (como RabbitMQ o ActiveMQ), Kafka no es una cola que destruye los mensajes una vez leídos. Kafka es un *registro de transacciones distribuido, particionado y replicado (Distributed Commit Log)*:

  • Rendimiento Masivo mediante E/S Secuencial: Escribe eventos de forma secuencial en disco y utiliza la llamada al sistema sendfile de Linux (Zero-Copy transfer) para transferir datos directamente desde la caché del sistema operativo a la tarjeta de red sin pasar por el espacio de usuario de la JVM.
  • *Rejugabilidad de Eventos (Event Replayability): Los consumidores controlan su propia posición de lectura (offset*), lo que permite a nuevos microservicios procesar el histórico completo de eventos desde el principio de los tiempos o reprocesar datos tras corregir un error de lógica de negocio.

Sin embargo, el despliegue de Kafka en entornos empresariales introduce un vector de riesgo severo si se opera con la configuración por defecto: cualquier cliente con conectividad de red al puerto 9092 puede suscribirse a topics con datos bancarios o PII, inyectar mensajes fraudulentos o alterar los offsets de los consumidores legítimos.


Arquitectura de Seguridad en Apache Kafka: Autenticación, ACLs y Cifrado

Garantizar la integridad y confidencialidad en un clúster Kafka requiere una defensa en profundidad que abarque desde la capa de transporte hasta la gobernanza del esquema de datos:

graph TD
    subgraph Client_Tier ["🖥️ Microservicios Spring Boot 3"]
        ProducerApp["Order Producer Service<br/>(Client Cert: order-producer.p12)"]
        ConsumerApp["Billing Consumer Service<br/>(Client Cert: billing-consumer.p12)"]
    end

    subgraph Kafka_Security_Perimeter ["🛡️ Perímetro de Seguridad Kafka (Puerto 9093 SSL)"]
        mTLS_Gateway["Terminación mTLS (Verificación de Certificados X.509)"]
        ACL_Engine["Motor de Autorización Kafka ACL (Mínimo Privilegio)"]
    end

    subgraph Kafka_Broker_Cluster ["⚙️ Clúster Kafka (KRaft Metadata)"]
        TopicOrders[("Topic: orders-v1<br/>3 Particiones / RF=3")]
        TopicDLT[("Topic: orders-v1.DLT<br/>Dead Letter Topic")]
    end

    subgraph Governance ["📋 Gobernanza de Datos"]
        SchemaRegistry["Confluent Schema Registry (Avro Validation)"]
    end

    ProducerApp -->|1. Validar Esquema Avro| SchemaRegistry
    ProducerApp -->|2. TCP 9093: Handshake mTLS| mTLS_Gateway
    mTLS_Gateway -->|3. Evaluar Permiso WRITE en orders-v1| ACL_Engine
    ACL_Engine -->|Autorizado: Append Log| TopicOrders

    ConsumerApp -->|Handshake mTLS + SASL/SCRAM| mTLS_Gateway
    mTLS_Gateway -->|Evaluar Permiso READ en orders-v1| ACL_Engine
    ACL_Engine -->|Autorizado: Poll Records| TopicOrders
    TopicOrders --> ConsumerApp

    ConsumerApp -.->|Fallo de Procesamiento / Reintentos Agotados| TopicDLT

    classDef client fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef perimeter fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
    classDef cluster fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
    classDef gov fill:#fff3e0,stroke:#f57c00,stroke-width:2px;

    class ProducerApp,ConsumerApp client;
    class mTLS_Gateway,ACL_Engine perimeter;
    class TopicOrders,TopicDLT cluster;
    class SchemaRegistry gov;

1. El Modelo de Amenazas en Mensajería Distribuida

En un clúster de mensajería no securizado, se presentan vectores de ataque críticos:

  1. *Intercepción y Espionaje (Eavesdropping):* El tráfico viaja en texto plano por la red interna. Si un atacante compromete un contenedor en la misma red, puede ejecutar volcados de tráfico con herramientas como Wireshark o tcpdump y capturar payloads completos con números de tarjeta o contraseñas, según demostramos en análisis de tráfico con Wireshark en contenedores.
  2. *Inyección de Mensajes Maliciosos (Poison Pills / Spoofing):* Productores no autorizados pueden publicar eventos falsificados (por ejemplo, autorizaciones de pago fraudulentas o payloads diseñados para provocar errores de deserialización NullPointerException en los consumidores).
  3. *Secuestro de Grupos de Consumo (Consumer Group Hijacking): Un cliente no autenticado puede unirse a un group.id legítimo, provocando un rebalanceo forzado de particiones (rebalance storm*) y robando mensajes antes de que lleguen a los consumidores autorizados.

2. Autenticación Criptográfica: mTLS y SASL/SCRAM-SHA-512

Kafka soporta múltiples mecanismos de autenticación a nivel de protocolo de transporte:

Opción A: Autenticación Mutua TLS (mTLS)

Tanto los brokers como los clientes intercambian certificados digitales X.509 validados contra una Autoridad de Certificación (CA) interna, como vimos en TLS y mTLS en Spring Boot 3.

  • Ventajas: Cifrado extremo a extremo de todo el tráfico en tránsito y autenticación criptográfica de máquina a máquina sin contraseñas.
  • Configuración en el Broker (server.properties):
listeners=SSL://0.0.0.0:9093
advertised.listeners=SSL://kafka.empresa.local:9093
ssl.keystore.location=/var/private/ssl/server.keystore.p12
ssl.keystore.password=KeystorePassword2026!
ssl.keystore.type=PKCS12
ssl.truststore.location=/var/private/ssl/server.truststore.p12
ssl.truststore.password=TruststorePassword2026!
ssl.truststore.type=PKCS12
# Exigir certificado de cliente obligatorio
ssl.client.auth=required
ssl.enabled.protocols=TLSv1.3

Opción B: SASL/SCRAM-SHA-512 sobre Canal Cifrado SSL (SASL_SSL)

Para entornos donde gestionar certificados de cliente para cada microservicio resulta operativo complejo, se utiliza SASL/SCRAM-SHA-512. Emplea un protocolo de desafío-respuesta con sal criptográfica (Salted Challenge Response Authentication Mechanism) donde las contraseñas nunca viajan por la red ni se almacenan en texto plano en los metadatos de KRaft.


3. Autorización Granular con Listas de Control de Acceso (Kafka ACLs)

La autenticación identifica al cliente (por ejemplo, User:CN=order-producer-service), pero no limita lo que puede hacer. La autorización se gobierna mediante Kafka ACLs.

Por defecto, se debe configurar el broker para denegar cualquier operación salvo autorización explícita:

authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
allow.everyone.if.no.acl.found=false
super.users=User:CN=admin-kafka-service

Configuración de Mínimo Privilegio con la CLI de Kafka:

Para el microservicio productor de pedidos (order-producer-service):

# Permitir únicamente ESCRIBIR en el topic orders-v1
kafka-acls --bootstrap-server kafka.empresa.local:9093 \
    --command-config admin-client.properties \
    --add \
    --allow-principal User:CN=order-producer-service \
    --operation WRITE \
    --operation DESCRIBE \
    --topic orders-v1

Para el microservicio consumidor de facturación (billing-consumer-service):

# Permitir LEER del topic orders-v1 exclusivamente bajo su grupo de consumo
kafka-acls --bootstrap-server kafka.empresa.local:9093 \
    --command-config admin-client.properties \
    --add \
    --allow-principal User:CN=billing-consumer-service \
    --operation READ \
    --operation DESCRIBE \
    --topic orders-v1 \
    --group billing-processing-group

Con esta política, si el microservicio de facturación es comprometido, un atacante no puede publicar mensajes falsos en orders-v1 ni unirse a otros grupos de consumo para espiar datos ajenos.


4. Integración en Spring Boot 3 con spring-kafka

En Spring Boot 3 (Java 21), la biblioteca spring-kafka permite configurar las propiedades criptográficas y de resiliencia de forma declarativa.

Configuración de Seguridad en application.yml:

spring:
  kafka:
    bootstrap-servers: kafka.empresa.local:9093
    security:
      protocol: SSL
    ssl:
      protocol: TLSv1.3
      key-store-location: classpath:certs/order-service-keystore.p12
      key-store-password: "${KAFKA_KEYSTORE_PASSWORD}"
      key-store-type: PKCS12
      trust-store-location: classpath:certs/ca-truststore.p12
      trust-store-password: "${KAFKA_TRUSTSTORE_PASSWORD}"
      trust-store-type: PKCS12

    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      acks: all # Garantizar persistencia en todos los In-Sync Replicas (ISR)
      properties:
        enable.idempotence: true # Productor idempotente: sin duplicados en reintentos
        retries: 5
        max.in.flight.requests.per.connection: 1

    consumer:
      group-id: billing-processing-group
      auto-offset-reset: earliest
      enable-auto-commit: false # Commit manual para garantizar procesamiento At-Least-Once
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
      properties:
        spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
        spring.json.trusted.packages: "es.secdevoops.dto"

5. Resiliencia y Manejo de Errores: Dead Letter Topics (DLT)

Un problema habitual en consumidores de Kafka es la "Poison Pill": un mensaje con formato JSON inválido o datos incompatibles que provoca una excepción en el consumidor. Si no se maneja adecuadamente, el consumidor reintenta indefinidamente el mismo mensaje en un bucle infinito, bloqueando la partición entera.

Spring Boot 3 proporciona DefaultErrorHandler con reintentos exponenciales y envío automático a un Dead Letter Topic (DLT):

package es.secdevoops.config;

import org.apache.kafka.common.TopicPartition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaOperations;
import org.springframework.kafka.listener.CommonErrorHandler;
import org.springframework.kafka.listener.DeadLetterPublishingRecoverer;
import org.springframework.kafka.listener.DefaultErrorHandler;
import org.springframework.util.backoff.ExponentialBackOff;

@Configuration
public class KafkaConsumerConfig {

    @Bean
    public CommonErrorHandler errorHandler(KafkaOperations<Object, Object> kafkaTemplate) {
        // Enviar a topic con sufijo .DLT tras agotar reintentos
        DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(
            kafkaTemplate,
            (record, ex) -> new TopicPartition(record.topic() + ".DLT", record.partition())
        );

        // Reintentar 3 veces con backoff exponencial: 1s, 2s, 4s
        ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);
        backOff.setMaxAttempts(3);

        DefaultErrorHandler handler = new DefaultErrorHandler(recoverer, backOff);
        // Excepciones fatales no reintentables (enviar directamente a DLT)
        handler.addNotRetryableExceptions(IllegalArgumentException.class);
        return handler;
    }
}

6. Gobernanza de Esquemas y Políticas de Retención (Schema Registry y Log Compaction)

En arquitecturas distribuidas enterprise, la seguridad de los datos también reside en la integridad de los contratos de esquema a lo largo del ciclo de vida del software.

Validación Estricta con Schema Registry

Cuando múltiples equipos desacoplados producen y consumen eventos en Kafka, un cambio no coordinado en la estructura del payload (como eliminar un campo obligatorio o alterar el tipo de datos) provoca fallos en cascada en los consumidores downstream.

Integrar Confluent Schema Registry o Apicurio Registry con esquemas Apache Avro o JSON Schema permite forzar modos de compatibilidad (BACKWARD, FORWARD o FULL):

# Configuración de Schema Registry en Spring Boot application.yml
spring:
  kafka:
    properties:
      schema.registry.url: "https://schema-registry.enterprise.local:8081"
      auto.register.schemas: false # En producción: prohibir registro automático sin aprobación en CI/CD
      use.latest.version: true

Establecer auto.register.schemas: false en producción es un control de seguridad esencial: impide que un productor modifique o registre esquemas arbitrarios en tiempo de ejecución, obligando a que cualquier evolución de esquema sea validada y aprobada previamente en el pipeline DevSecOps.

Políticas de Retención: Compaction vs. Deletion

A nivel de infraestructura de broker, la política de limpieza de particiones (cleanup.policy) debe elegirse según la semántica del negocio:

  • cleanup.policy=delete: Descarta segmentos antiguos según su antigüedad (retention.ms, ej. 7 días) o tamaño total (retention.bytes). Adecuado para logs de auditoría transitorios y eventos de telemetría.
  • cleanup.policy=compact: Retiene perpetuamente el último valor emitido para cada clave (key). Si se publica un registro con payload null (denominado tombstone), Kafka elimina la clave completamente en la siguiente pasada de compactación, lo que permite implementar el derecho al olvido (GDPR) de forma determinista en flujos de datos inmutables.

Resumen y Checklist de Hardening para Apache Kafka

  • [x] Conexiones en texto plano deshabilitadas; tráfico obligatorio sobre puerto SSL 9093 con TLS 1.3.
  • [x] Autenticación mutua mTLS o SASL/SCRAM-SHA-512 configurada en todos los clientes y brokers.
  • [x] Política de autorización por defecto cerrada (allow.everyone.if.no.acl.found=false).
  • [x] ACLs granulares aplicadas a nivel de Topic (WRITE o READ) y Consumer Group.
  • [x] Productor configurado con enable.idempotence=true y acks=all para garantizar integridad de datos.
  • [x] Consumidor protegido con ErrorHandlingDeserializer y Dead Letter Topics para evitar bloqueos por payloads corruptos.

Si necesitas auditar la arquitectura de mensajería distribuida de tu organización, implementar autenticación mTLS y ACLs en clústeres de Apache Kafka o diseñar pipelines de eventos seguros con Spring Boot, puedes consultar mis Servicios de Ciberseguridad y Consultoría o ponerte en contacto conmigo a través de Sobre mí.