Skip to main content

Command Palette

Search for a command to run...

Protegiendo nuestras API's de manera inteligente.

Una manera eficiente de manejar un incidente, de punta a punta, con Wazuh y N8N. ¿El limite? La imaginación.

Updated
12 min read
Protegiendo nuestras API's de manera inteligente.
S

I have a bachelor's degree in Technology from the University of Palermo, a Master in Information Security from the University of Murcia and different certifications such as CISSP | CISM | CDPSE | CCSK | CSX | MCSA | SMAC™️ | DSOE | DEPC | CSFPC | CSFPC | 5x AWS Certified.

He is currently CISO at Klar, a Mexican Fintech. He was fortunate to be awarded as CISO of the Year in Argentina in 2021 and was among the Top 100 CISO's in the World in 2022.

A lover of new technologies, he has developed a career in DevSecOps and Cloud Security at Eko Party, the largest security conference in Latin America.

Introducción

Generé una API Dummy de cuentas bancarias y para protegerla vamos a tener API Manager Kong al frente, reportando los log’s de las consultas que recibe a Wazuh. Si tenemos coincidencia con algunas de las reglas que cargaremos en Wazuh, se responderá al evento. Esa respuesta será dada por N8N, ejecutando un flujo de trabajo donde realizaremos diferentes pasos, como agregar la dirección del atacante en una lista negra de Kong o enviar un mensaje o, por qué no, levantar el caso en The Hive. Como si fuera poco agregamos Konga para la gestión grafica de Kong.

Arquitectura

Antes de eso deberían descargar la configuración de Wazuh Docker, para esta ocasión uso single-node-stack.

git clone https://github.com/wazuh/wazuh-docker.git -b v4.13.1

Generamos los certificados.

cd wazuh-docker/single-node/
docker compose -f generate-indexer-certs.yml run --rm generator

Una vez que lo corramos, podemos modificar le docker-compose original, con todo el stack necesario para esta prueba de concepto.

# Wazuh App Copyright (C) 2017, Wazuh Inc. (License GPLv2)
services:
  wazuh.manager:
    image: wazuh/wazuh-manager:4.13.1
    hostname: wazuh.manager
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 655360
        hard: 655360
    ports:
      - "1514:1514"
      - "1515:1515"
      - "514:514/udp"
      - "55000:55000"
    environment:
      - INDEXER_URL=https://wazuh.indexer:9200
      - INDEXER_USERNAME=admin
      - INDEXER_PASSWORD=SecretPassword
      - FILEBEAT_SSL_VERIFICATION_MODE=none
      - SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
      - SSL_CERTIFICATE=/etc/ssl/filebeat.pem
      - SSL_KEY=/etc/ssl/filebeat.key
      - API_USERNAME=wazuh-wui
      - API_PASSWORD=MyS3cr37P450r.*-
    volumes:
      - wazuh_api_configuration:/var/ossec/api/configuration
      - wazuh_etc:/var/ossec/etc
      - wazuh_logs:/var/ossec/logs
      - wazuh_queue:/var/ossec/queue
      - wazuh_var_multigroups:/var/ossec/var/multigroups
      - wazuh_integrations:/var/ossec/integrations
      - wazuh_active_response:/var/ossec/active-response/bin
      - wazuh_agentless:/var/ossec/agentless
      - wazuh_wodles:/var/ossec/wodles
      - filebeat_etc:/etc/filebeat
      - filebeat_var:/var/lib/filebeat
      - ./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key
      - ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf
      - ./config/wazuh_cluster/kong-rules.xml:/var/ossec/etc/rules/kong-rules.xml:ro
      - ./config/wazuh_cluster/0374-kong-decoder.xml:/var/ossec/etc/decoders/0374-kong-decoder.xml:ro # Agrego el decoder para Kong.
      - ./config/wazuh_cluster/custom-n8n:/var/ossec/integrations/custom-n8n:ro

    networks:
      - poc-network
  wazuh.indexer:
    image: wazuh/wazuh-indexer:4.13.1
    hostname: wazuh.indexer
    restart: always
    ports:
      - "9200:9200"
    environment:
      - "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - wazuh-indexer-data:/var/lib/wazuh-indexer
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.key
      - ./config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.pem
      - ./config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem
      - ./config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/certs/admin-key.pem
      - ./config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
      - ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml
    networks:
      - poc-network
  wazuh.dashboard:
    image: wazuh/wazuh-dashboard:4.13.1
    hostname: wazuh.dashboard
    restart: always
    ports:
      - 443:5601
    environment:
      - INDEXER_USERNAME=admin
      - INDEXER_PASSWORD=SecretPassword
      - WAZUH_API_URL=https://wazuh.manager
      - DASHBOARD_USERNAME=kibanaserver
      - DASHBOARD_PASSWORD=kibanaserver
      - API_USERNAME=wazuh-wui
      - API_PASSWORD=MyS3cr37P450r.*-
    volumes:
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
      - ./config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
      - ./config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml
      - wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config
      - wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom
    depends_on:
      - wazuh.indexer
    links:
      - wazuh.indexer:wazuh.indexer
      - wazuh.manager:wazuh.manager
    networks:
      - poc-network

# ============================================
# KONG DATABASE
# ============================================
  kong-database:
    image: postgres:15-alpine
    container_name: kong-database
    restart: always
    networks:
      - poc-network
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: kongpass
    ports:
      - "5432:5432"
    volumes:
      - kong-database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong"]
      interval: 10s
      timeout: 5s
      retries: 5

# ============================================
# KONG MIGRATIONS
# ============================================
  kong-migrations:
    image: kong:3.5
    container_name: kong-migrations
    command: kong migrations bootstrap
    networks:
      - poc-network
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kongpass
      KONG_PG_DATABASE: kong
    depends_on:
      kong-database:
        condition: service_healthy
    restart: on-failure

# ============================================
# KONG API GATEWAY
# ============================================
  kong:
      image: kong:3.5
      container_name: kong
      restart: unless-stopped
      networks:
        - poc-network
      environment:
        KONG_DATABASE: postgres
        KONG_PG_HOST: kong-database
        KONG_PG_USER: kong
        KONG_PG_PASSWORD: kongpass
        KONG_PG_DATABASE: kong
        KONG_PROXY_ACCESS_LOG: /dev/stdout
        KONG_ADMIN_ACCESS_LOG: /dev/stdout
        KONG_PROXY_ERROR_LOG: /dev/stderr
        KONG_ADMIN_ERROR_LOG: /dev/stderr
        KONG_ADMIN_LISTEN: 0.0.0.0:8001
        KONG_ADMIN_GUI_LISTEN: 0.0.0.0:8002
        KONG_PROXY_LISTEN: 0.0.0.0:8000, 0.0.0.0:8443 ssl
      ports:
        - "8000:8000"
        - "8443:8443"
        - "8001:8001"
        - "8002:8002"
      depends_on:
        kong-database:
          condition: service_healthy
        kong-migrations:
          condition: service_completed_successfully
      healthcheck:
        test: ["CMD", "kong", "health"]
        interval: 10s
        timeout: 10s
        retries: 10

# ============================================
# KONGA DATABASE
# ============================================
  konga-database:
    image: postgres:9.6-alpine
    container_name: konga-database
    restart: always
    networks:
      - poc-network
    environment:
      POSTGRES_USER: konga
      POSTGRES_DB: konga
      POSTGRES_PASSWORD: kongapass
    ports:
      - "5433:5432"
    volumes:
      - konga-database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "konga"]
      interval: 10s
      timeout: 5s
      retries: 5

# ============================================
# KONGA PREPARE
# ============================================
  konga-prepare:
    image: pantsel/konga:latest
    container_name: konga-prepare
    command: "-c prepare -a postgres -u postgresql://konga:kongapass@konga-database:5432/konga"
    networks:
      - poc-network
    environment:
      DB_ADAPTER: postgres
      DB_HOST: konga-database
      DB_USER: konga
      DB_PASSWORD: kongapass
      DB_DATABASE: konga
    depends_on:
      konga-database:
        condition: service_healthy
    restart: on-failure

# ============================================
# KONGA GUI
# ============================================
  konga:
    image: pantsel/konga:latest
    container_name: konga
    restart: always
    networks:
      - poc-network
    environment:
      DB_ADAPTER: postgres
      DB_HOST: konga-database
      DB_PORT: 5432
      DB_USER: konga
      DB_PASSWORD: kongapass
      DB_DATABASE: konga
      NODE_ENV: production
      KONGA_HOOK_TIMEOUT: 120000
      DB_PG_SCHEMA: public
      TOKEN_SECRET: change-this-secret-token
    ports:
      - "1337:1337"
    depends_on:
      konga-database:
        condition: service_healthy
      konga-prepare:
        condition: service_completed_successfully
      kong:
        condition: service_healthy

# ============================================
# BANKING API
# ============================================
  banking-api:
    image: safernandez666/banking-api
    container_name: banking-api
    restart: always
    networks:
      - poc-network
    ports:
      - "3000:3000"
    volumes:
      - banking-data:/app

# ============================================
# LOGSPOUT - LOG FORWARDER
# ============================================
  logspout:
    image: gliderlabs/logspout:latest
    container_name: logspout
    restart: always
    networks:
      - poc-network
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: syslog://wazuh.manager:514
    environment:
      - SYSLOG_FORMAT=rfc3164
      - LOGSPOUT=ignore
      - FILTER_NAME=kong|banking-api  # ← AGREGAR: Solo Kong y Banking API
    depends_on:
      - wazuh.manager
# ============================================
# N8N
# ============================================
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=n8n_password
      - N8N_HOST=0.0.0.0
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - WEBHOOK_URL=http://n8n:5678/
      - GENERIC_TIMEZONE=America/Argentina/Buenos_Aires
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - poc-network
# ============================================
# OLLAMA
# ============================================
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11435:11434" # Expose Ollama's API port
    volumes:
      - ollama_data:/root/.ollama # Persistent storage for models and data
    restart: unless-stopped
    networks:
      - poc-network
# ============================================
# NETWORKS
# ============================================
networks:
  poc-network:
    driver: bridge

# ============================================
# VOLUMES
# ============================================
volumes:
  wazuh_api_configuration:
  wazuh_etc: 
  wazuh_logs:
  wazuh_queue:
  wazuh_var_multigroups:
  wazuh_integrations:
  wazuh_active_response:
  wazuh_agentless:
  wazuh_wodles:
  filebeat_etc:
  filebeat_var:
  wazuh-indexer-data:
  wazuh-dashboard-config:
  wazuh-dashboard-custom:
  kong-database-data:
  konga-database-data:
  banking-data:
  n8n_data:
  ollama_data:

Importante edistar ./config/wazuh_cluster/wazuh_manager.conf, para que lo tome Wazuh Manager como su ossec.conf. Le agregamos esto:

  <!-- ============================================ -->
  <!-- Configuración de receptor remoto (Syslog) -->
  <!-- ============================================ -->
  <remote>
    <connection>syslog</connection>
    <port>514</port>
    <protocol>udp</protocol>
    <allowed-ips>any</allowed-ips>
  </remote>

  <!-- ============================================ -->
  <!-- INTEGRACIÓN CON N8N - WEBHOOKS PARA KONG -->
  <!-- ============================================ -->

  <!-- Webhook específico para ATAQUES (nivel 8+) -->
  <integration>
    <name>custom-n8n</name>
    <level>8</level>
    <group>kong,attack</group>
    <alert_format>json</alert_format>
  </integration>

También vamos agregar la integración custom-n8n en ./config/wazuh_cluster/.

#!/bin/bash
# Wazuh to n8n Integration

N8N_WEBHOOK_URL="http://n8n:5678/webhook/wazuh-kong-alerts"
LOG_FILE="/var/ossec/logs/integrations.log"

# Wazuh pasa el archivo de alerta como primer argumento
ALERT_FILE="$1"

# Timestamp
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Log
echo "[${TIMESTAMP}] Processing alert from file: ${ALERT_FILE}" >> ${LOG_FILE}

# Leer el contenido del archivo
if [ -f "${ALERT_FILE}" ]; then
    ALERT_JSON=$(cat "${ALERT_FILE}")

    # Enviar a n8n
    HTTP_CODE=$(curl -X POST "${N8N_WEBHOOK_URL}" \
      -H "Content-Type: application/json" \
      -d "${ALERT_JSON}" \
      -w "%{http_code}" \
      -s \
      -o /dev/null \
      --connect-timeout 10 \
      --max-time 30)

    if [ "${HTTP_CODE}" == "200" ] || [ "${HTTP_CODE}" == "201" ]; then
        echo "[${TIMESTAMP}] SUCCESS - HTTP ${HTTP_CODE}" >> ${LOG_FILE}
        exit 0
    else
        echo "[${TIMESTAMP}] FAILED - HTTP ${HTTP_CODE}" >> ${LOG_FILE}
        exit 1
    fi
else
    echo "[${TIMESTAMP}] ERROR - Alert file not found: ${ALERT_FILE}" >> ${LOG_FILE}
    exit 1
fi

Es muy importante que a custom-n8n le hagan un chmod 555, para que pueda ser ejecutado por cualquiera. Esto en local, antes de levantar el contenedor.

Agregamos el docoder y las regla, también en /config/wazuh_cluster. El decoder se llamara 0374-kong-decoder.xml y las regla kong-rules.xml.

<!--
  -  Kong API Manager custom decoder
  -  Created for extracting srcip from Kong logs
  -  Author: Santiago Fernandez
  -  Date: October 2025
  -
  -  This decoder extracts:
  -    - srcip: Source IP address
  -    - protocol: HTTP method (GET, POST, etc.)
  -    - url: Requested URL
  -    - id: HTTP status code
-->

<decoder name="kong-decoder">
    <prematch>^\S+ \S+ kong[\d+]:</prematch>
    <regex>^\S+ \S+ kong[\d+]: (\S+) \S+ \S+ [\S+ \S+] "(\w+) (\S+) HTTP\S+" (\d+) \d+ \S+ \S+</regex>
    <order>srcip, protocol, url, id</order>
</decoder>
<!--
  -  Kong API Gateway Rules
  -  Author: Santiago Fernandez
  -  Date: October 2025
  -  
  -  Rule ID Range: 100100 - 100199
-->

<group name="kong,">

  <!-- ============================================ -->
  <!-- REGLA BASE - Cualquier tráfico de Kong -->
  <!-- ============================================ -->
  <rule id="100100" level="3">
    <decoded_as>kong-decoder</decoded_as>
    <description>Kong: API Gateway traffic detected</description>
    <group>kong,access</group>
  </rule>

  <!-- ============================================ -->
  <!-- REGLAS PARA BANKING API -->
  <!-- ============================================ -->

  <!-- Accesos exitosos (2xx) a banking -->
  <rule id="100101" level="5">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^2\d\d$</id>
    <description>Kong: Successful access to banking API - $(protocol) $(url) - Status: $(id) from $(srcip)</description>
    <group>kong,banking,success</group>
  </rule>

  <!-- Errores del cliente (4xx) en banking -->
  <rule id="100102" level="7">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^4\d\d$</id>
    <description>Kong: Client error on banking API - $(protocol) $(url) - Status: $(id) from $(srcip)</description>
    <group>kong,banking,error,client_error</group>
  </rule>

  <!-- Errores del servidor (5xx) en banking -->
  <rule id="100103" level="10">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^5\d\d$</id>
    <description>Kong: Server error on banking API - $(protocol) $(url) - Status: $(id)</description>
    <group>kong,banking,critical,server_error</group>
  </rule>

  <!-- ============================================ -->
  <!-- REGLAS DE CORRELACIÓN - Detección de ataques -->
  <!-- ============================================ -->

  <!-- Múltiples errores 4xx desde la misma IP (posible escaneo) -->
  <rule id="100104" level="8" frequency="5" timeframe="60">
    <if_matched_sid>100102</if_matched_sid>
    <same_source_ip />
    <description>Kong: Multiple failed requests ($(id)) from $(srcip) to banking API - Possible scanning or brute force attempt</description>
    <group>kong,banking,attack,multiple_errors,scanning</group>
  </rule>

  <!-- Múltiples errores 5xx (problema en el servidor) -->
  <rule id="100105" level="9" frequency="3" timeframe="120">
    <if_matched_sid>100103</if_matched_sid>
    <description>Kong: Multiple server errors on banking API - Service may be down or experiencing issues</description>
    <group>kong,banking,critical,service_degradation</group>
  </rule>

  <!-- ============================================ -->
  <!-- ENDPOINTS SENSIBLES -->
  <!-- ============================================ -->

  <!-- Acceso a endpoints de cuentas -->
  <rule id="100106" level="6">
    <if_sid>100101</if_sid>
    <url>/banking/cuentas</url>
    <description>Kong: Access to accounts endpoint - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,sensitive,accounts</group>
  </rule>

  <!-- Acceso a endpoints de transacciones -->
  <rule id="100107" level="6">
    <if_sid>100101</if_sid>
    <url>/banking/transacciones</url>
    <description>Kong: Access to transactions endpoint - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,sensitive,transactions</group>
  </rule>

  <!-- Acceso a endpoints de transferencias -->
  <rule id="100108" level="7">
    <if_sid>100101</if_sid>
    <url>/banking/transferencias</url>
    <description>Kong: Access to money transfer endpoint - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,sensitive,transfers,high_risk</group>
  </rule>

  <!-- ============================================ -->
  <!-- MÉTODOS HTTP ESPECÍFICOS -->
  <!-- ============================================ -->

  <!-- Peticiones DELETE en banking (alta sensibilidad) -->
  <rule id="100110" level="8">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <protocol>DELETE</protocol>
    <description>Kong: DELETE request on banking API - $(url) from $(srcip)</description>
    <group>kong,banking,modification,delete,high_risk</group>
  </rule>

  <!-- Peticiones PUT/PATCH en banking -->
  <rule id="100111" level="6">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <protocol>PUT|PATCH</protocol>
    <description>Kong: Modification request on banking API - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,modification</group>
  </rule>

  <!-- Peticiones POST en banking -->
  <rule id="100112" level="5">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <protocol>POST</protocol>
    <id>^2\d\d$</id>
    <description>Kong: POST request on banking API - $(url) from $(srcip)</description>
    <group>kong,banking,creation</group>
  </rule>

  <!-- ============================================ -->
  <!-- CÓDIGOS DE ESTADO ESPECÍFICOS -->
  <!-- ============================================ -->

  <!-- 401 - No autorizado -->
  <rule id="100120" level="7">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^401$</id>
    <description>Kong: Unauthorized access attempt to banking API - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,authentication,unauthorized</group>
  </rule>

  <!-- Múltiples 401 desde la misma IP -->
  <rule id="100121" level="9" frequency="3" timeframe="60">
    <if_matched_sid>100120</if_matched_sid>
    <same_source_ip />
    <description>Kong: Multiple unauthorized access attempts from $(srcip) - Possible credential stuffing attack</description>
    <group>kong,banking,attack,authentication,credential_stuffing</group>
  </rule>

  <!-- 403 - Prohibido -->
  <rule id="100122" level="7">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^403$</id>
    <description>Kong: Forbidden access attempt to banking API - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,authorization,forbidden</group>
  </rule>

  <!-- 404 - No encontrado -->
  <rule id="100123" level="5">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^404$</id>
    <description>Kong: Resource not found on banking API - $(protocol) $(url) from $(srcip)</description>
    <group>kong,banking,not_found</group>
  </rule>

  <!-- Múltiples 404 desde la misma IP (posible escaneo de endpoints) -->
  <rule id="100124" level="8" frequency="10" timeframe="120">
    <if_matched_sid>100123</if_matched_sid>
    <same_source_ip />
    <description>Kong: Multiple 404 errors from $(srcip) - Possible endpoint enumeration attack</description>
    <group>kong,banking,attack,enumeration</group>
  </rule>

  <!-- 429 - Too Many Requests (Rate Limiting) -->
  <rule id="100125" level="6">
    <if_sid>100100</if_sid>
    <url>/banking/</url>
    <id>^429$</id>
    <description>Kong: Rate limit exceeded on banking API from $(srcip)</description>
    <group>kong,banking,rate_limit,abuse</group>
  </rule>

  <!-- ============================================ -->
  <!-- TRÁFICO NO BANKING (Otros endpoints) -->
  <!-- ============================================ -->

  <!-- Accesos exitosos a otros endpoints -->
  <rule id="100130" level="3">
    <if_sid>100100</if_sid>
    <id>^2\d\d$</id>
    <description>Kong: Successful request - $(protocol) $(url) from $(srcip)</description>
    <group>kong,success</group>
  </rule>

  <!-- Errores en otros endpoints -->
  <rule id="100131" level="5">
    <if_sid>100100</if_sid>
    <id>^4\d\d$</id>
    <description>Kong: Client error - $(protocol) $(url) - Status: $(id) from $(srcip)</description>
    <group>kong,error</group>
  </rule>

</group>

Listo! Ya estamos para ejecutar.

docker-compose up -d

Vamos a pegarle una mirada a los contenedores.

Para que tengamos una idea de los puertos y contenedores, te los paso en limpio aquí.

Banking API

Es una API generada para este Prueba de Concepto con información ficticia. Pueden encontrar el código en el repositorio, pero a grandes rasgos nos data esta información:

  • Gestiona clientes (nombre, email, teléfono, fecha de registro).

  • Gestiona cuentas vinculadas a clientes (número, tipo, saldo, moneda).

  • Gestiona tarjetas de crédito asociadas a cuentas (número, límite, saldo usado, vencimiento). En las respuestas públicas el CVV se oculta.

  • Registra transacciones (depósitos, retiros, transferencias, compras).

  • Provee un resumen por cliente que agrega cuentas y tarjetas (totales de saldos y crédito disponible).

  • Incluye endpoints de salud y la documentación.

Kong

Voy a proporcionarles los comandos para que nuestro API Manager consuma la Banking API. Vamos paso a paso: primero, debemos crear un "Service" en Kong que represente tu API backend.

curl -i -X POST http://localhost:8001/services/ \
  --data "name=banking-api" \
  --data "url=http://banking-api:3000"

Creamos un "Route" que define cómo los clientes accederán a tu servicio. Vincula la ruta al servicio creado anteriormente.

curl -i -X POST http://localhost:8001/services/banking-api/routes \
  --data "name=banking-route" \
  --data "paths[]=/banking" \
  --data "strip_path=true"

Habilitamos CORS (Cross-Origin Resource Sharing) para permitir peticiones desde navegadores. Aca lo voy a crear de una forma permisiva.

curl -i -X POST http://localhost:8001/services/banking-api/plugins \
  --data "name=cors" \
  --data "config.origins=*" \
  --data "config.methods[]=GET" \
  --data "config.methods[]=HEAD" \
  --data "config.methods[]=PUT" \
  --data "config.methods[]=PATCH" \
  --data "config.methods[]=POST" \
  --data "config.methods[]=DELETE" \
  --data "config.methods[]=OPTIONS" \
  --data "config.headers[]=*" \
  --data "config.exposed_headers[]=*" \
  --data "config.credentials=true" \
  --data "config.max_age=3600"

Como saben, Kong es un API Manager. Podríamos habilitar varios plugins, pero para la PoC no son necesarios. Podrías habilitar Rate Limiting, Request Size Limiting, Correlation ID o Request Transformer. Vamos a probarlo.

# Vamos a ver los clientes
curl http://localhost:8000/banking/clientes | jq
# Cliente con ID 1
curl http://localhost:8000/banking/clientes/1 | jq

Konga

Todo lo que hicimos con curl, podemos hacerlo a través de la interfaz gráfica. Primero, vamos a crear nuestra cuenta en Konga y luego agregar la conexión para gestionar Kong.

Ahí se ve todo lo creado, anteriormente. Por ejemplo el servicio.

o la ruta.

Wazuh

Si todo salió bien, ingresamos a Wazuh para ver nuestras consultas. Para ver las reglas de Kong, aplique un filtro decoder.name: kong-decoder

N8N

Ya levantado el contenedor ingresamos a http://localhost:5678. Ahí armaremos dos workflows, uno para el análisis y otro para que los administradores puedan liberar la ip bloqueada.

Una vez que suframos el ataque, habrá lógica para decidir si debemos bloquear o no. Esto es una prueba, pero imagino consultas de diferentes API, Slack o servicios internos. Una vez que se ejecute el Workflow Block IP, deberíamos recibir un correo como este:

Al final del correo, podemos invocar el Workflow Release IP para liberar la dirección del atacante.

Simulacion de Ataque

Les dejo un script para simular un ataque rápidamente.

#!/bin/bash
# quick-test.sh - Prueba rpida

echo " Generando alertas de prueba..."

# Trfico normal
curl -s http://localhost:8000/banking/cuentas

# Mltiples 404 (activar regla de correlacin)
for i in {1..12}; do
    curl -s http://localhost:8000/banking/fake-endpoint-$i
    sleep 0.5
done

# Mltiples 401 (credential stuffing)
for i in {1..5}; do
    curl -s http://localhost:8000/banking/cuentas -H "Authorization: Bearer fake_$i"
    sleep 1
done

# DELETE peligroso
curl -s -X DELETE http://localhost:8000/banking/cuentas/123

echo " Alertas generadas. Revisa n8n y Wazuh!"

Espero que les sirva y puedan usarlo en sus empresas, si necesitan una mano me avisan.

Saludos, Santi.

Bonus Track

Dejo los Worflows de N8N para analizar con Ollama. Código.

Comandos Útiles

# 1. Reiniciar Wazuh Manager
docker-compose restart single-node-wazuh.manager-1

# 2. Verificar que está escuchando en el puerto 514
docker exec single-node-wazuh.manager-1 netstat -tlnp | grep 514

# 3. Revisar ossec.conf
docker exec single-node-wazuh.manager-1 cat /var/ossec/etc/ossec.conf

# 4. Ver logs de Integraciones
docker exec single-node-wazuh.manager-1 tail -f /var/ossec/logs/integrations.log

# 5. Ver logs de Alarmas de Kong
docker exec -it single-node-wazuh.manager-1 tail -f /var/ossec/logs/alerts/alerts.log | grep -A 10 "kong"

# 6. Ver logs de Wazuh Manager
docker logs -f single-node-wazuh.indexer-1

Referencias

https://ipv6.rs/tutorial/Ubuntu_Server_Latest/Kong/

https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html