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
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%.
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]
- Trigger: Webhook/API desde SIEM
- Enrichment: Enriquecimiento con threat intel
- Assessment: Evaluación automática de riesgo
- Response: Acciones automatizadas
- Notification: Alertas a equipos
- Tracking: Creación de tickets
Workflow Completo: Alerta SIEM → Respuesta Automática
{
"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"
} Componentes Críticos del Workflow
1. Trigger Webhook Inteligente
El webhook no solo recibe alertas, sino que las valida y filtra:
// 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
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.
3. Respuestas Automatizadas por Nivel
| Nivel | Acciones Automáticas |
|---|---|
| CRITICAL | Bloqueo IP + Slack + Jira + Escalamiento |
| HIGH | Slack + Jira + Monitorización extendida |
| MEDIUM | Jira + Log detallado |
| LOW | Solo logging |
Métricas y Monitorización
KPIs del Workflow
-- 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:
- Tiempo de respuesta promedio: < 2 minutos objetivo
- Tasa de falsos positivos: < 5% objetivo
- Cobertura de automatización: > 90% objetivo
- Disponibilidad del workflow: > 99.5% objetivo
Casos de Uso Adicionales
1. Phishing Email Response
{
"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
Nunca implementes respuestas automáticas destructivas (bloqueos, borrados) sin al menos 2 semanas de testing en modo “dry-run”.
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étrica | Manual | Automatizado | Mejora |
|---|---|---|---|
| Tiempo respuesta promedio | 45 min | 1.5 min | 97% |
| Coste por incidente | €250 | €15 | 94% |
| Falsos positivos procesados | 100% | 8% | 92% |
| Disponibilidad 24/7 | No | Sí | ∞ |
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:
- Empieza simple: Un workflow de notificación es mejor que ninguno
- Mide todo: Sin métricas, no hay mejora
- Itera rápido: Workflows evolucionan con amenazas
- Forma al equipo: La herramienta es tan buena como quien la usa
- Instala n8n en un entorno de pruebas
- Importa el workflow de ejemplo
- Configura tu primer webhook SIEM
- ¡Empieza a automatizar!
¿Quieres implementar automatización de seguridad en tu SOC? Contáctame para una consultoría personalizada.