Back to Integrations

Wazuh Integration

Enrich your Wazuh SIEM alerts with real-time IP threat intelligence from ipinsights.io.

Overview

Wazuh is a free, open-source XDR and SIEM platform used for threat detection, integrity monitoring, incident response and compliance. By integrating ipinsights.io you can automatically enrich source-IP fields in Wazuh alerts with reputation scores, geolocation, ASN data and blocklist membership — giving analysts the context they need without leaving the console.

The integration uses Wazuh's active-response framework to call the ipinsights.io API whenever an alert fires, writing enrichment data back as a local log that Wazuh can decode and correlate with custom rules.

Architecture Overview

The diagram below shows how data flows through the integration:

┌──────────────┐ alert ┌──────────────────┐ HTTP GET ┌──────────────────────┐ │ Wazuh Agent │ ─────────────▶ │ Wazuh Manager │ ────────────▶ │ ipinsights.io API │ │ (endpoint) │ │ (ossec.conf) │ │ /api/v1/lookup │ └──────────────┘ └────────┬─────────┘ └──────────┬───────────┘ │ │ │ active-response │ JSON response ▼ ▼ ┌──────────────────┐ ┌──────────────────────┐ │ Enrichment │ ◀──────────── │ IP reputation, │ │ Script (bash) │ parse & │ geolocation, ASN, │ └────────┬─────────┘ enrich │ blocklists │ │ └──────────────────────┘ │ writes JSON log ▼ ┌──────────────────┐ │ Custom Decoder │ │ & Alert Rules │ └──────────────────┘
  1. A Wazuh agent detects an event (e.g. failed SSH login) and forwards the alert to the manager.
  2. The manager triggers the active-response enrichment script for qualifying alerts.
  3. The script extracts the source IP, queries the ipinsights.io API and writes the enriched result as a JSON log.
  4. Wazuh's custom decoder parses the enrichment log, and custom rules generate secondary alerts based on threat scores.

Prerequisites

  • Wazuh 4.x or later (manager + agent deployed)
  • An ipinsights.io API key — available on your profile page (or register for free)
  • curl and jq installed on the Wazuh manager
  • Outbound HTTPS (port 443) access from the manager to https://ipinsights.io

Step 1 — Create the Enrichment Script

Create the active-response script on the Wazuh manager at /var/ossec/active-response/bin/ipinsights-enrich.sh:

#!/bin/bash # ───────────────────────────────────────────────────────────── # ipinsights-enrich.sh — Wazuh active-response enrichment # Queries the ipinsights.io API and writes enrichment data # to a local log for Wazuh to decode. # ───────────────────────────────────────────────────────────── API_KEY="YOUR_API_KEY_HERE" API_URL="https://ipinsights.io/api/v1/lookup" LOG_FILE="/var/ossec/logs/ipinsights-enrichment.log" CACHE_DIR="/var/ossec/tmp/ipinsights_cache" CACHE_TTL=3600 # cache results for 1 hour mkdir -p "$CACHE_DIR" # Read the alert JSON from stdin (Wazuh active-response protocol) read -r INPUT_JSON # Extract the source IP from the alert SRCIP=$(echo "$INPUT_JSON" | jq -r '.parameters.alert.data.srcip // empty') if [ -z "$SRCIP" ]; then echo "$(date '+%Y-%m-%d %T') ipinsights: no srcip found in alert" >> "$LOG_FILE" exit 0 fi # Check cache first CACHE_FILE="$CACHE_DIR/$(echo -n "$SRCIP" | md5sum | cut -d' ' -f1).json" if [ -f "$CACHE_FILE" ]; then FILE_AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") )) if [ "$FILE_AGE" -lt "$CACHE_TTL" ]; then RESPONSE=$(cat "$CACHE_FILE") fi fi # Query the API if no cached result if [ -z "${RESPONSE:-}" ]; then RESPONSE=$(curl -s -m 10 -H "X-API-Key: $API_KEY" \ "$API_URL?ip=$SRCIP" 2>/dev/null) if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then echo "$(date '+%Y-%m-%d %T') ipinsights: API request failed for $SRCIP" >> "$LOG_FILE" exit 1 fi # Cache the response echo "$RESPONSE" > "$CACHE_FILE" fi # Extract key fields THREAT_SCORE=$(echo "$RESPONSE" | jq -r '.data.threat_score // "N/A"') COUNTRY=$(echo "$RESPONSE" | jq -r '.data.country_code // "N/A"') ASN=$(echo "$RESPONSE" | jq -r '.data.asn // "N/A"') ISP=$(echo "$RESPONSE" | jq -r '.data.isp // "N/A"') IS_TOR=$(echo "$RESPONSE" | jq -r '.data.is_tor // false') IS_VPN=$(echo "$RESPONSE" | jq -r '.data.is_vpn // false') IS_PROXY=$(echo "$RESPONSE" | jq -r '.data.is_proxy // false') BLOCKLISTS=$(echo "$RESPONSE" | jq -r '.data.blocklist_count // 0') # Write enrichment log line echo "$(date '+%Y-%m-%d %T') ipinsights: ip=$SRCIP threat_score=$THREAT_SCORE country=$COUNTRY asn=$ASN isp=\"$ISP\" is_tor=$IS_TOR is_vpn=$IS_VPN is_proxy=$IS_PROXY blocklists=$BLOCKLISTS" >> "$LOG_FILE" exit 0

Make the script executable:

sudo chmod 750 /var/ossec/active-response/bin/ipinsights-enrich.sh sudo chown root:wazuh /var/ossec/active-response/bin/ipinsights-enrich.sh

Step 2 — Register the Active-Response Command

Add the following <command> block inside your /var/ossec/etc/ossec.conf on the manager:

<ossec_config> <command> <name>ipinsights-enrich</name> <executable>ipinsights-enrich.sh</executable> <timeout_allowed>no</timeout_allowed> </command> </ossec_config>

Step 3 — Configure the Active Response

Still in ossec.conf, add an <active-response> block to trigger the enrichment script on alerts at or above a chosen rule level:

<ossec_config> <active-response> <command>ipinsights-enrich</command> <location>server</location> <level>6</level> </active-response> </ossec_config>

Adjust <level> to control which alerts trigger enrichment. Level 6 covers most authentication-related events without being too noisy.

Step 4 — Add a Custom Decoder Optional

Create /var/ossec/etc/decoders/ipinsights.xml so Wazuh can parse the enrichment log:

<decoder name="ipinsights"> <prematch>^ipinsights: </prematch> </decoder> <decoder name="ipinsights-fields"> <parent>ipinsights</parent> <regex>ip=(\S+) threat_score=(\S+) country=(\S+) asn=(\S+) isp="(\.+)" is_tor=(\S+) is_vpn=(\S+) is_proxy=(\S+) blocklists=(\S+)</regex> <order>srcip,threat_score,country,asn,isp,is_tor,is_vpn,is_proxy,blocklists</order> </decoder>

Step 5 — Add Alert Rules Optional

Create /var/ossec/etc/rules/ipinsights_rules.xml to fire alerts based on enrichment results:

<group name="ipinsights,"> <!-- Base rule for every enrichment log line --> <rule id="100100" level="3"> <decoded_as>ipinsights</decoded_as> <description>IP Insights enrichment received.</description> </rule> <!-- High threat score (≥ 75) --> <rule id="100101" level="10"> <if_sid>100100</if_sid> <regex>threat_score=(7[5-9]|[89]\d|100)</regex> <description>IP Insights: High threat score ($threat_score) for $(srcip).</description> </rule> <!-- Tor exit node --> <rule id="100102" level="8"> <if_sid>100100</if_sid> <regex>is_tor=true</regex> <description>IP Insights: Source IP $(srcip) is a known Tor exit node.</description> </rule> <!-- IP appears on multiple blocklists --> <rule id="100103" level="9"> <if_sid>100100</if_sid> <regex>blocklists=([3-9]|\d{2,})</regex> <description>IP Insights: Source IP $(srcip) found on $(blocklists) blocklists.</description> </rule> </group>

Step 6 — Restart Wazuh Manager

Apply the changes by restarting the Wazuh manager service:

sudo systemctl restart wazuh-manager

Check /var/ossec/logs/ossec.log for any configuration errors after the restart.

Verification

After restarting, verify the integration is working:

  1. Check the enrichment log — trigger an alert (e.g. a failed SSH login) and look for output:
    tail -f /var/ossec/logs/ipinsights-enrichment.log
  2. Test the script manually — pipe a mock alert into the script:
    echo '{"parameters":{"alert":{"data":{"srcip":"8.8.8.8"}}}}' | \ /var/ossec/active-response/bin/ipinsights-enrich.sh
  3. Verify decoder parsing — use the Wazuh log-test utility:
    sudo /var/ossec/bin/wazuh-logtest
    Paste a sample enrichment log line and confirm the fields are decoded correctly.
  4. Check Wazuh dashboard — search for rule.groups:ipinsights to see enriched alerts in the UI.

Best Practices

  • Cache API responses — the enrichment script above caches results for one hour in /var/ossec/tmp/ipinsights_cache/. This avoids redundant API calls and helps you stay within your rate limit. Adjust CACHE_TTL to suit your environment.
  • Handle errors gracefully — always check the curl exit code and the success field in the API response before extracting data. Log failures to make troubleshooting easier.
  • Be aware of rate limits — the default free-tier limit is 600 requests per hour. If you have a high-volume environment, consider requesting a higher limit using the form below or adjusting the <level> threshold in your active-response configuration to reduce the number of enrichments triggered.
  • Secure your API key — restrict read access to the enrichment script (chmod 750) and consider using Wazuh's /var/ossec/etc/local_internal_options.conf or environment variables to store the key outside of the script body.
  • Rotate cache periodically — set up a cron job to purge stale cache files:
    0 * * * * find /var/ossec/tmp/ipinsights_cache/ -type f -mmin +60 -delete

Optional — Contribute Detections Back

Intelligence is more useful when it flows in both directions. If you'd like to help grow the dataset, drop the following companion script alongside the enrichment one to POST high-confidence attacker IPs back to /api/v1/report. Submissions are weighted by your reporter reputation, which evolves over time as your reports are corroborated by other intelligence sources. See the API documentation for the full list of allowed category tags.

Create /var/ossec/active-response/bin/ipinsights-report.sh:

#!/bin/bash # ───────────────────────────────────────────────────────────── # ipinsights-report.sh — Wazuh active-response reciprocal upload # Submits the source IP of a high-confidence alert back to # ipinsights.io as a community report. # ───────────────────────────────────────────────────────────── set -eu API_KEY="YOUR_API_KEY_HERE" API_URL="https://ipinsights.io/api/v1/report" LOG_FILE="/var/ossec/logs/ipinsights-report.log" # Active-response feeds JSON on stdin INPUT_JSON=$(cat) SRCIP=$(echo "$INPUT_JSON" | jq -r '.parameters.alert.data.srcip // empty') GROUPS=$(echo "$INPUT_JSON" | jq -r '.parameters.alert.rule.groups // [] | join(",")') [ -z "$SRCIP" ] && exit 0 # Map Wazuh rule groups to ipinsights.io categories. CATEGORIES="other" case "$GROUPS" in *authentication_failed*|*ssh*) CATEGORIES="ssh_bruteforce" ;; *win_authentication_failed*|*rdp*) CATEGORIES="rdp_bruteforce" ;; *web_attack*|*web_scan*|*sql_injection*) CATEGORIES="web_attack" ;; *recon*|*nmap*|*portscan*) CATEGORIES="port_scan" ;; *exploit_attempt*) CATEGORIES="exploit_attempt" ;; esac PAYLOAD=$(jq -n \ --arg ip "$SRCIP" \ --arg cat "$CATEGORIES" \ --arg src "wazuh" \ '{ip:$ip, categories:($cat|split(",")), source:$src}') RESPONSE=$(curl -s -m 10 -X POST \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ "$API_URL" 2>/dev/null || true) echo "$(date '+%Y-%m-%d %T') ipinsights-report: ip=$SRCIP categories=$CATEGORIES response=$RESPONSE" >> "$LOG_FILE" exit 0

Register it as a second active-response command, scoped to a higher rule level (e.g. <level>10</level>) so only high-confidence detections are submitted:

<ossec_config> <command> <name>ipinsights-report</name> <executable>ipinsights-report.sh</executable> <timeout_allowed>no</timeout_allowed> </command> <active-response> <command>ipinsights-report</command> <location>server</location> <level>10</level> </active-response> </ossec_config>

Reciprocal uploads are entirely opt-in. The endpoint refuses private/reserved addresses and rate-limits repeated reports of the same IP from the same key.

Troubleshooting

No enrichment log output

  • Confirm the script is executable: ls -l /var/ossec/active-response/bin/ipinsights-enrich.sh
  • Ensure ownership is root:wazuh.
  • Check /var/ossec/logs/ossec.log for active-response errors.
  • Verify the <level> threshold — if set too high, alerts may not trigger the script.

API request failures

  • Test connectivity from the manager: curl -I https://ipinsights.io
  • Verify your API key is valid on your profile page.
  • Check for HTTP 429 (rate limit) responses — if frequent, consider increasing your limit.
  • Inspect the enrichment log for API request failed messages.

Decoder not parsing fields

  • Validate the decoder XML: sudo /var/ossec/bin/wazuh-logtest
  • Ensure the log format written by the script exactly matches the <regex> in the decoder.
  • Add /var/ossec/logs/ipinsights-enrichment.log as a <localfile> in ossec.conf:
    <localfile> <log_format>syslog</log_format> <location>/var/ossec/logs/ipinsights-enrichment.log</location> </localfile>

Custom rules not firing

  • Make sure the rule file is loaded — check /var/ossec/logs/ossec.log for XML errors.
  • Verify rule IDs don't conflict with existing rules (use IDs above 100000).
  • Use wazuh-logtest to confirm the rule triggers against a sample log line.

API Key: You can find your API key on your profile page. Don't have an account yet? Register for free.

Request Higher API Limit

Running a high-volume Wazuh deployment? If the default rate limit isn't enough for your environment, submit a request below and we'll review it.

Maximum 2,000 characters.