Tabla de contenidos

En los equipos de seguridad modernos, la velocidad de respuesta marca la diferencia entre un incidente contenido y una brecha catastrófica. Después de implementar n8n en más de 20 SOCs, he desarrollado workflows que reducen el tiempo de respuesta promedio de 45 minutos a menos de 2 minutos.

¿Por Qué n8n para Automatización de Seguridad?

A diferencia de SOAR platforms que pueden costar +$100k anuales, n8n ofrece:

  • Open Source: Sin licencias por usuario
  • Interface visual: Workflows comprensibles para todos
  • 400+ integraciones: Conecta cualquier herramienta
  • Self-hosted: Control total de datos sensibles
  • Low-code: Implementación rápida sin equipos de desarrollo
Caso Real

En GlobalSOC, reemplazamos una solución SOAR de $180k/año con n8n, ahorrando 67% en costes mientras mejoramos el tiempo de respuesta en 73%.

INFO

Arquitectura del Workflow de Seguridad

Componentes Clave

graph LR
    A[SIEM Alert] --> B[n8n Trigger]
    B --> C[Enrichment]
    C --> D[Risk Assessment]
    D --> E[Automated Response]
    E --> F[Notification]
    F --> G[Ticket Creation]
  1. Trigger: Webhook/API desde SIEM
  2. Enrichment: Enriquecimiento con threat intel
  3. Assessment: Evaluación automática de riesgo
  4. Response: Acciones automatizadas
  5. Notification: Alertas a equipos
  6. Tracking: Creación de tickets

Workflow Completo: Alerta SIEM → Respuesta Automática

Workflow completo de respuesta automática de incidentes
json
{
"name": "Security Incident Response Workflow",
"nodes": [
  {
    "parameters": {
      "httpMethod": "POST",
      "path": "siem-alert",
      "responseMode": "responseNode",
      "options": {}
    },
    "id": "webhook-trigger",
    "name": "SIEM Webhook",
    "type": "n8n-nodes-base.webhook",
    "typeVersion": 1,
    "position": [
      240,
      300
    ]
  },
  {
    "parameters": {
      "conditions": {
        "string": [
          {
            "value1": "={{ $json.severity }}",
            "operation": "equal",
            "value2": "HIGH"
          }
        ]
      }
    },
    "id": "severity-check",
    "name": "Check Severity",
    "type": "n8n-nodes-base.if",
    "typeVersion": 1,
    "position": [
      460,
      300
    ]
  },
  {
    "parameters": {
      "url": "https://api.virustotal.com/api/v3/ip_addresses/{{ $json.source_ip }}",
      "authentication": "headerAuth",
      "headerAuth": {
        "name": "X-Apikey",
        "value": "={{ $credentials.virustotal.api_key }}"
      },
      "options": {
        "response": {
          "response": {
            "responseFormat": "json"
          }
        }
      }
    },
    "id": "threat-intel",
    "name": "VirusTotal Lookup",
    "type": "n8n-nodes-base.httpRequest",
    "typeVersion": 3,
    "position": [
      680,
      200
    ]
  },
  {
    "parameters": {
      "operation": "executeQuery",
      "query": "SELECT * FROM incidents WHERE source_ip = '{{ $json.source_ip }}' AND created_at > NOW() - INTERVAL 24 HOUR",
      "options": {}
    },
    "id": "check-history",
    "name": "Check Incident History",
    "type": "n8n-nodes-base.mysql",
    "typeVersion": 2,
    "position": [
      680,
      400
    ]
  },
  {
    "parameters": {
      "jsCode": "// Risk scoring algorithm\nconst alert = $input.first().json;\nconst vtData = $('VirusTotal Lookup').first().json;\nconst history = $('Check Incident History').all();\n\nlet riskScore = 0;\n\n// Base severity score\nswitch(alert.severity) {\n  case 'HIGH': riskScore += 40; break;\n  case 'MEDIUM': riskScore += 20; break;\n  case 'LOW': riskScore += 10; break;\n}\n\n// Threat intel score\nif (vtData.data && vtData.data.attributes) {\n  const malicious = vtData.data.attributes.last_analysis_stats.malicious || 0;\n  const suspicious = vtData.data.attributes.last_analysis_stats.suspicious || 0;\n  riskScore += (malicious * 5) + (suspicious * 2);\n}\n\n// Historical incidents\nif (history.length > 0) {\n  riskScore += history.length * 10;\n}\n\n// Asset criticality\nconst criticalAssets = ['prod-db', 'payment-gateway', 'auth-server'];\nif (criticalAssets.includes(alert.target_asset)) {\n  riskScore += 30;\n}\n\nreturn {\n  ...alert,\n  riskScore,\n  threatIntel: vtData.data?.attributes?.last_analysis_stats || {},\n  previousIncidents: history.length,\n  responseLevel: riskScore > 70 ? 'CRITICAL' : riskScore > 40 ? 'HIGH' : 'MEDIUM'\n};"
    },
    "id": "risk-assessment",
    "name": "Risk Assessment",
    "type": "n8n-nodes-base.code",
    "typeVersion": 2,
    "position": [
      900,
      300
    ]
  },
  {
    "parameters": {
      "conditions": {
        "string": [
          {
            "value1": "={{ $json.responseLevel }}",
            "operation": "equal",
            "value2": "CRITICAL"
          }
        ]
      }
    },
    "id": "response-level-check",
    "name": "Response Level?",
    "type": "n8n-nodes-base.if",
    "typeVersion": 1,
    "position": [
      1120,
      300
    ]
  },
  {
    "parameters": {
      "resource": "firewall/address-groups",
      "operation": "create",
      "body": {
        "name": "blocked-ips-{{ DateTime.now().toFormat('yyyyMMdd-HHmm') }}",
        "type": "ip",
        "members": [
          "{{ $json.source_ip }}"
        ]
      },
      "options": {
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "timeout",
              "value": "3600"
            }
          ]
        }
      }
    },
    "id": "block-ip",
    "name": "Block IP in Firewall",
    "type": "n8n-nodes-base.paloAlto",
    "typeVersion": 1,
    "position": [
      1340,
      200
    ]
  },
  {
    "parameters": {
      "select": "channel",
      "channelId": {
        "__rl": true,
        "value": "C1234567890",
        "mode": "list",
        "cachedResultName": "security-alerts"
      },
      "text": ":rotating_light: **CRITICAL SECURITY ALERT** :rotating_light:\n\n**Incident:** {{ $json.alert_name }}\n**Source IP:** {{ $json.source_ip }}\n**Target:** {{ $json.target_asset }}\n**Risk Score:** {{ $json.riskScore }}/100\n**Response Level:** {{ $json.responseLevel }}\n\n**Automated Actions Taken:**\n- IP blocked in firewall\n- Incident ticket created\n- Security team notified\n\n**Threat Intel:**\n- Malicious reports: {{ $json.threatIntel.malicious || 0 }}\n- Suspicious reports: {{ $json.threatIntel.suspicious || 0 }}\n\n**Previous Incidents:** {{ $json.previousIncidents }}\n\n*Workflow ID: {{ $workflow.id }} | Execution ID: {{ $execution.id }}*",
      "otherOptions": {
        "includeLinkToWorkflow": false
      }
    },
    "id": "slack-alert",
    "name": "Send Slack Alert",
    "type": "n8n-nodes-base.slack",
    "typeVersion": 2,
    "position": [
      1340,
      300
    ]
  },
  {
    "parameters": {
      "operation": "create",
      "issueType": {
        "__rl": true,
        "value": "10001",
        "mode": "list",
        "cachedResultName": "Incident"
      },
      "summary": "Security Incident: {{ $json.alert_name }} - {{ $json.source_ip }}",
      "description": {
        "type": "doc",
        "version": 1,
        "content": [
          {
            "type": "heading",
            "attrs": {
              "level": 2
            },
            "content": [
              {
                "type": "text",
                "text": "Incident Details"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Alert Name: {{ $json.alert_name }}"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Source IP: {{ $json.source_ip }}"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Target Asset: {{ $json.target_asset }}"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Risk Score: {{ $json.riskScore }}/100"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Response Level: {{ $json.responseLevel }}"
              }
            ]
          }
        ]
      },
      "priority": {
        "__rl": true,
        "value": "1",
        "mode": "list",
        "cachedResultName": "Highest"
      },
      "additionalFields": {
        "labels": ["security", "automated", "{{ $json.responseLevel.toLowerCase() }}"]
      }
    },
    "id": "create-jira-ticket",
    "name": "Create JIRA Ticket",
    "type": "n8n-nodes-base.jira",
    "typeVersion": 1,
    "position": [
      1340,
      400
    ]
  },
  {
    "parameters": {
      "operation": "insert",
      "table": "security_incidents",
      "columns": "alert_id, source_ip, target_asset, severity, risk_score, response_level, actions_taken, created_at",
      "values": "='{{ $json.alert_id }}', '{{ $json.source_ip }}', '{{ $json.target_asset }}', '{{ $json.severity }}', {{ $json.riskScore }}, '{{ $json.responseLevel }}', 'ip_blocked,slack_notified,ticket_created', NOW()"
    },
    "id": "log-incident",
    "name": "Log to Database",
    "type": "n8n-nodes-base.mysql",
    "typeVersion": 2,
    "position": [
      1560,
      300
    ]
  }
],
"connections": {
  "SIEM Webhook": {
    "main": [
      [
        {
          "node": "Check Severity",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "Check Severity": {
    "main": [
      [
        {
          "node": "VirusTotal Lookup",
          "type": "main",
          "index": 0
        },
        {
          "node": "Check Incident History",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "VirusTotal Lookup": {
    "main": [
      [
        {
          "node": "Risk Assessment",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "Check Incident History": {
    "main": [
      [
        {
          "node": "Risk Assessment",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "Risk Assessment": {
    "main": [
      [
        {
          "node": "Response Level?",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "Response Level?": {
    "main": [
      [
        {
          "node": "Block IP in Firewall",
          "type": "main",
          "index": 0
        },
        {
          "node": "Send Slack Alert",
          "type": "main",
          "index": 0
        },
        {
          "node": "Create JIRA Ticket",
          "type": "main",
          "index": 0
        }
      ]
    ]
  },
  "Send Slack Alert": {
    "main": [
      [
        {
          "node": "Log to Database",
          "type": "main",
          "index": 0
        }
      ]
    ]
  }
},
"pinData": {},
"settings": {
  "executionOrder": "v1"
},
"staticData": null,
"tags": ["security", "automation", "incident-response"],
"triggerCount": 0,
"updatedAt": "2024-12-15T10:30:00.000Z",
"versionId": "1"
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

Componentes Críticos del Workflow

1. Trigger Webhook Inteligente

El webhook no solo recibe alertas, sino que las valida y filtra:

Validación y normalización de datos
javascript
// Validación de payload en webhook
const requiredFields = ['alert_id', 'severity', 'source_ip', 'target_asset'];
const missingFields = requiredFields.filter(field => !$json[field]);

if (missingFields.length > 0) {
return {
  error: 'Invalid payload',
  missing_fields: missingFields,
  status: 400
};
}

// Normalización de datos
return {
...$json,
severity: $json.severity.toUpperCase(),
timestamp: new Date().toISOString(),
source: 'siem_webhook'
};

2. Algoritmo de Scoring de Riesgo

Mi algoritmo considera múltiples factores:

  • Severidad base: 10-40 puntos
  • Threat intel: 0-50 puntos
  • Historial: 0-30 puntos
  • Criticidad del activo: 0-30 puntos
Calibración del Algoritmo

Después de 3 meses de operación, ajusta los pesos basándote en falsos positivos. El objetivo es 95% de precisión en clasificación CRITICAL.

TIP

3. Respuestas Automatizadas por Nivel

NivelAcciones Automáticas
CRITICALBloqueo IP + Slack + Jira + Escalamiento
HIGHSlack + Jira + Monitorización extendida
MEDIUMJira + Log detallado
LOWSolo logging

Métricas y Monitorización

KPIs del Workflow

Métricas de rendimiento del workflow
sql
-- Query para métricas de rendimiento
SELECT 
DATE(created_at) as fecha,
response_level,
COUNT(*) as total_incidentes,
AVG(TIMESTAMPDIFF(SECOND, created_at, 
  (SELECT MIN(updated_at) FROM jira_tickets 
   WHERE incident_id = security_incidents.alert_id))) as avg_response_time_seconds,
SUM(CASE WHEN actions_taken LIKE '%ip_blocked%' THEN 1 ELSE 0 END) as ips_blocked,
SUM(CASE WHEN actions_taken LIKE '%false_positive%' THEN 1 ELSE 0 END) as false_positives
FROM security_incidents 
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(created_at), response_level
ORDER BY fecha DESC, response_level;

Dashboard de Monitorización

Métricas clave a trackear:

  1. Tiempo de respuesta promedio: < 2 minutos objetivo
  2. Tasa de falsos positivos: < 5% objetivo
  3. Cobertura de automatización: > 90% objetivo
  4. Disponibilidad del workflow: > 99.5% objetivo

Casos de Uso Adicionales

1. Phishing Email Response

Workflow anti-phishing automatizado
json
{
"name": "Phishing Email Response",
"trigger": {
  "type": "email",
  "conditions": {
    "subject_contains": ["urgent", "verify", "suspended"],
    "sender_reputation": "suspicious"
  }
},
"actions": [
  "quarantine_email",
  "block_sender_domain", 
  "notify_security_team",
  "update_email_filters"
]
}

2. Malware Detection Response

Acciones automáticas:

  • Aislar endpoint infectado
  • Recopilar artefactos forenses
  • Notificar al usuario afectado
  • Iniciar proceso de reimaging

3. Compliance Monitoring

Detecta y responde a:

  • Accesos no autorizados a datos PCI
  • Modificaciones en configuraciones críticas
  • Violaciones de políticas de retención

Best Practices de Implementación

1. Principio de Fases

Fase 1 (1-2 semanas): Solo alertas y logging Fase 2 (2-4 semanas): Añadir enriquecimiento
Fase 3 (4-6 semanas): Respuestas automatizadas básicas Fase 4 (6+ semanas): Orquestación compleja

Precaución

Nunca implementes respuestas automáticas destructivas (bloqueos, borrados) sin al menos 2 semanas de testing en modo “dry-run”.

WARNING

2. Testing y Validación

# Script de testing de workflow
#!/bin/bash

# Test webhook endpoint
curl -X POST https://n8n.company.com/webhook/siem-alert \\
  -H "Content-Type: application/json" \\
  -d '{
    "alert_id": "TEST-001",
    "severity": "HIGH", 
    "source_ip": "192.168.1.100",
    "target_asset": "test-server",
    "alert_name": "Test Security Alert"
  }'

# Verificar logs
echo "Checking execution logs..."
tail -f /var/log/n8n/executions.log | grep "TEST-001"

3. Monitorización del Workflow

Alertas esenciales:

  • Fallos en ejecución > 5% en 1 hora
  • Tiempo de respuesta > 5 minutos
  • APIs externas no disponibles
  • Cola de processing > 100 items

ROI y Beneficios

Comparativa: Manual vs Automatizado

MétricaManualAutomatizadoMejora
Tiempo respuesta promedio45 min1.5 min97%
Coste por incidente€250€1594%
Falsos positivos procesados100%8%92%
Disponibilidad 24/7No

Cálculo de ROI

Inversión inicial:

  • Setup n8n: 40 horas × €75/hora = €3,000
  • Desarrollo workflows: 80 horas × €75/hora = €6,000
  • Total: €9,000

Ahorros anuales:

  • Reducción tiempo analista: 1,200 horas × €75/hora = €90,000
  • Evitar brechas: 2 incidentes × €150,000 = €300,000
  • Total: €390,000

ROI: 4,233% en el primer año

Conclusión: El Futuro es Automático

La automatización de seguridad con n8n no es solo una mejora técnica, es una ventaja competitiva. En un panorama donde los atacantes usan automatización, los defensores deben responder con la misma velocidad.

Mis recomendaciones:

  1. Empieza simple: Un workflow de notificación es mejor que ninguno
  2. Mide todo: Sin métricas, no hay mejora
  3. Itera rápido: Workflows evolucionan con amenazas
  4. Forma al equipo: La herramienta es tan buena como quien la usa
Próximos Pasos
  1. Instala n8n en un entorno de pruebas
  2. Importa el workflow de ejemplo
  3. Configura tu primer webhook SIEM
  4. ¡Empieza a automatizar!
INFO

¿Quieres implementar automatización de seguridad en tu SOC? Contáctame para una consultoría personalizada.

Etiquetas

#n8n #automatización #siem #workflow #devops