--- license: apache-2.0 base_model: NousResearch/Hermes-3-Llama-3.1-8B tags: - lora - peft - cisco - networking - security - wazuh - incident-response - network-automation - restconf - ospf - bgp language: - en pipeline_tag: text-generation --- # Wazuh-LLM Cisco Network Incident Response Automated network incident response system for Cisco IOS-XE routers and switches. Takes Wazuh SIEM alerts, classifies them with a fine-tuned LLM, queries live device state via RESTCONF, generates CLI fix commands using domain-specific LoRA adapters, and applies the fixes automatically via RESTCONF PATCH. Two backends are provided: a **manual 3-script pipeline** for operator-controlled workflows, and a **fully autonomous daemon** that watches Wazuh logs and resolves incidents end-to-end without human intervention. --- ## Architecture ### Backend 1 — Manual Pipeline (3 scripts) ``` Wazuh alerts file | v [runners/prefilter.py] filter level-12, drop auth noise | v [runners/pipeline.py] Stage 1 Wazuh LLM (Ollama) incident_type, severity, IOCs Stage 2 Show command lookup deterministic table (incident -> show cmds) Stage 3 RESTCONF mapping deterministic table (show cmd -> YANG path) Stage 4 Domain routing selects correct LoRA adapter | v creates show_outputs/alert_NNN_DEVICE_INCIDENT/ restconf_commands.json <- execute these GETs against routers alert_info.json (operator executes RESTCONF GETs and drops response JSONs into the folder) | v [runners/run_fix.py] polls folders, runs domain LoRA | |-- fix_commands.txt CLI fix commands (human readable) +-- restconf_fix_commands.json RESTCONF PATCH ops to apply the fix +-- pipeline_fix_output.jsonl full record of all alerts processed ``` ### Backend 2 — Autonomous Daemon (always-on) ``` /var/ossec/logs/alerts/alerts.json (Wazuh live output, tailed continuously) | v [backend/daemon.py] Watch tail alerts file, detect new entries (level 7-12) Detect SSH brute-force: 5+ failures from same IP within 5-minute window | v per alert, fully automatic: [1] Extract device IP -> device map [2] Classify Ollama wazuh-llama -> incident_type, IOCs, severity [3] Show commands deterministic table lookup [4] RESTCONF GETs execute live against device, collect YANG state [5] Domain LoRA generate CLI fix commands [6] RESTCONF PATCHes apply fixes directly to device [7] Log result managed_incidents.jsonl + managed_incidents.log | v per-alert folder: restconf_get_results.json what the device reported fix_commands.txt CLI commands the LLM generated restconf_fix_commands.json PATCH ops with YANG bodies patch_results.json HTTP status of each PATCH ``` --- ## LoRA Adapters 18 domain-specific adapters fine-tuned on Hermes-3-Llama-3.1-8B (r=8, alpha=32): | Domain | Adapter | Incidents covered | |---------|----------------------------------|--------------------------------------------------------| | OSPF | `incidents/ospf/ospf1` | Neighbor down, full-to-down, adjacency loss | | OSPF | `incidents/ospf/ospf2` | ExStart/Exchange stuck, Init stuck, 2-Way | | OSPF | `incidents/ospf/ospf3` | Auth, hello/dead mismatch, area, network-type, MTU | | OSPF | `incidents/ospf/ospf4` | LSA flood, LSDB inconsistency, redistribution | | BGP | `incidents/bgp/lora_llm_bgp1` | Session flap, hold-timer expiry, neighbor reset | | BGP | `incidents/bgp/lora_llm_bgp2` | Prefix limit, route leak, path selection | | BGP | `incidents/bgp/lora_llm_bgp3` | Missing RIB routes, next-hop-self, AFI-SAFI mismatch | | Sec | `incidents/security/lora_llm_sec1` | ACL blocking legitimate traffic | | Sec | `incidents/security/lora_llm_sec2` | ACL misconfiguration (protocol, wildcard, direction)| | Sec | `incidents/security/lora_llm_sec3` | Excessive denies, brute force, port scan | | Switch | `incidents/switch/lora_llm_switch1` | MAC flapping, STP topology change, err-disable | | Switch | `incidents/switch/lora_llm_switch2` | Port security, duplex/speed mismatch | | Switch | `incidents/switch/lora_llm_switch3` | VLAN mismatch, trunk negotiation, storm control | | Service | `incidents/service/lora_llm_service1` | DHCP conflict, starvation, IP conflict | | Service | `incidents/service/lora_llm_service2` | DNS failure, NTP unsync, ARP spoofing | | Sys | `incidents/sys/lora_llm_sys1` | High CPU, memory exhaustion, process crash | | Sys | `incidents/sys/lora_llm_sys2` | Interface flap, duplex/speed mismatch | | Sys | `incidents/sys/lora_llm_sys3` | Reload, environmental, general system health | --- ## Requirements ```bash pip install -r requirements.txt ``` The Wazuh LLM stage uses **Ollama** with a local `wazuh-llama` model: ```bash # Install Ollama: https://ollama.com ollama create wazuh-llama -f Modelfile ``` --- ## Quick Start — Manual Pipeline ```bash # Step 1 — pre-filter raw Wazuh export python runners/prefilter.py --input alerts.json --output s_alerts.jsonl # Step 2 — classify + build RESTCONF show commands + create per-alert folders python runners/pipeline.py --input s_alerts.jsonl --show-dir show_outputs # Step 3 — execute the RESTCONF GETs listed in each folder's restconf_commands.json # and save JSON responses into the same folder # Step 4 — run domain LoRA, generate fix commands + RESTCONF PATCH ops python runners/run_fix.py --show-dir show_outputs ``` Per-alert output folder after all steps: ``` show_outputs/alert_001_R1_ospf_neighbor_down/ alert_info.json alert metadata restconf_commands.json GET commands (written by pipeline.py) show_ip_ospf_neighbor.json RESTCONF GET response (you drop this) fix_commands.txt CLI fix commands (written by run_fix.py) restconf_fix_commands.json RESTCONF PATCH ops (written by run_fix.py) ``` --- ## Quick Start — Autonomous Daemon ```bash # Standard — watch live Wazuh alerts, auto-apply fixes (level 7-12) python backend/daemon.py # Dry run — classify + generate commands, but do NOT PATCH devices python backend/daemon.py --dry-run # Custom alert file and thresholds python backend/daemon.py \ --alerts /var/ossec/logs/alerts/alerts.json \ --min-level 7 \ --ssh-threshold 5 \ --ssh-window 300 \ --poll 3 ``` The daemon tail-follows the Wazuh alerts file and processes each alert automatically end-to-end with no operator involvement. ### Daemon output files | File | Content | |------|---------| | `managed_incidents.jsonl` | Machine-readable: one JSON record per handled alert | | `managed_incidents.log` | Human-readable: one summary block per alert | | `daemon.log` | Full debug log of all daemon activity | | `show_outputs/alert_NNN_*/` | Per-alert audit folder (GET results, fix, patch results) | ### SSH Brute-Force Detection The daemon tracks failed SSH login alerts independently of the level filter. When **5 or more failures from the same source IP occur within 5 minutes**, a synthetic `ssh_brute_force` incident is triggered and the full pipeline runs — even if each individual alert is below the level threshold. Tune with `--ssh-threshold N --ssh-window SECONDS`. --- ## Device Configuration Edit the maps at the top of `runners/pipeline.py` and `backend/daemon.py` to match your network: ```python SOURCE_IP_DEVICE_MAP = { "10.10.10.10": "R1", "2.2.2.2": "R2", # ... } DEVICE_MGMT_IP = { "R1": "10.10.10.10", # management IP used for RESTCONF "R2": "2.2.2.2", # ... } ``` Router credentials: `ROUTER_USER` / `ROUTER_PASS` constants (default: `admin` / `cisco123!`). --- ## RESTCONF All communication uses RESTCONF over HTTPS (port 443) with Cisco IOS-XE YANG models. **Read (GET)** — `_RESTCONF_RULES` table maps each show command to its YANG path: ``` show ip ospf neighbor -> /restconf/data/Cisco-IOS-XE-ospf-oper:ospf-oper-data/ospf-state show ip bgp summary -> /restconf/data/Cisco-IOS-XE-bgp-oper:bgp-state-data/bgp-route-vrfs show interfaces -> /restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces ... ``` **Write (PATCH)** — CLI fix commands are parsed into context blocks and converted to RESTCONF PATCH operations with YANG-native JSON bodies: ``` interface GigabitEthernet0/0 -> PATCH .../interface/GigabitEthernet=0%2F0 ip ospf hello-interval 10 body: Cisco-IOS-XE-ospf:ospf hello-interval 10 router bgp 65001 -> PATCH .../router/bgp=65001 neighbor 10.0.0.2 timers 30 90 body: neighbor timers keepalive 30 holdtime 90 ip access-list extended INBOUND -> PATCH .../ip/access-list/extended=INBOUND ``` Supported YANG modules include: `Cisco-IOS-XE-ospf-oper`, `Cisco-IOS-XE-bgp-oper`, `Cisco-IOS-XE-interfaces-oper`, `Cisco-IOS-XE-acl-oper`, `Cisco-IOS-XE-spanning-tree-oper`, `Cisco-IOS-XE-vlan-oper`, `Cisco-IOS-XE-dhcp-oper`, `Cisco-IOS-XE-arp-oper`, `Cisco-IOS-XE-nat-oper`, `Cisco-IOS-XE-ntp-oper`, `Cisco-IOS-XE-process-cpu-oper`, `Cisco-IOS-XE-process-memory-oper`, `Cisco-IOS-XE-native` --- ## Training Training scripts and datasets are in `training/` and `datasets/`. Each adapter was trained on 900–1500 examples with LoRA (r=8, alpha=32, dropout=0.1) targeting `q_proj` and `v_proj` on Hermes-3-Llama-3.1-8B. ```bash python training/train_ospf1.py python training/train_bgp1.py # etc. ``` --- ## Lab Topology Tested on a 4-router + 4-switch Cisco IOS-XE lab: - **R1 / R2 / R3 / R4** — IOS-XE routers running OSPF + BGP - **SW1 / SW2** — distribution switches - **ACCESS-SW1 / ACCESS-SW2** — access switches - **R4** — DHCP server (192.168.40.0/24 pool) - Wazuh manager collecting syslog from all devices --- ## Project Files | Path | Description | |------|-------------| | `runners/prefilter.py` | Pre-filter raw Wazuh export (level filter + brute-force detection) | | `runners/pipeline.py` | Manual backend Phase 1: classify, map RESTCONF GETs, create folders | | `runners/run_fix.py` | Manual backend Phase 2: run domain LoRA, generate and map fix commands | | `backend/daemon.py` | Autonomous daemon: watches live Wazuh alerts, resolves incidents end-to-end | | `Modelfile` | Ollama model definition for the Wazuh alert classifier | | `models/base/` | Hermes-3-Llama-3.1-8B base model | | `models/incidents/` | 18 domain-specific LoRA adapters | | `models/registry.json` | Adapter name -> path registry | | `datasets/` | Training datasets (JSONL, one incident type per file) | | `training/` | Training scripts (one per adapter) | | `cases/` | Incident test cases used for evaluation | --- ## Citation ```bibtex @misc{wazuh-llm-cisco-2025, title = {Wazuh-LLM Cisco Network Incident Response}, year = {2025}, url = {https://huggingface.co/YOUR_USERNAME/wazuh-llm-cisco} } ```