CR-2026-02-26 - Wazuh SIEM Network Integration
Comprehensive integration of all network infrastructure with Wazuh SIEM (10.50.1.134:514) for centralized security monitoring, compliance logging, and incident response.
| CR ID |
CR-2026-02-26-001 |
| Status |
In Progress |
| Priority |
P1 |
| Requester |
evanusmodestus |
| Date |
2026-02-26 |
Executive Summary
| Metric | Value |
|---|---|
Total Devices |
19 (6 network + 7 servers + 3 workstations + 3 k8s) |
Integration Method |
Syslog (network devices) + Wazuh Agent (servers/workstations) |
Wazuh Endpoints |
Indexer: 10.50.1.131:9200 |
Current Blocker |
Archives not indexing in OpenSearch (Filebeat pipeline) |
Session Log
Session 1: netapi pfsense syslog Commands
|
Problem: pfSense REST API v2 doesn’t expose syslog configuration. Solution: SSH-based commands using PHP execution on pfSense. |
# Show current syslog configuration
netapi pfsense syslog show
# Enable remote syslog (default: Wazuh)
netapi pfsense syslog enable --server 10.50.1.134 --categories filter,system
# Enable all log categories
netapi pfsense syslog enable --categories all
# Disable remote syslog
netapi pfsense syslog disable
2c4b9ff feat(pfsense): Add syslog management commands via SSH
Session 2: pfSense Syslog Enabled
netapi pfsense syslog enable
✓ Remote syslog enabled Server: 10.50.1.134 Categories: filter,system
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- tail -5 /var/ossec/logs/archives/archives.log"
# PASTE OUTPUT HERE
Infrastructure Inventory
Network Infrastructure (Syslog)
| Device | Type | IP | Status | Notes |
|---|---|---|---|---|
pfSense-01 |
Firewall |
10.50.1.1 |
SENDING |
Data reaching manager, not indexed |
ISE-01 |
NAC |
10.50.1.20 |
PENDING |
ERS API syslog target |
9800-WLC |
Wireless |
10.50.1.40 |
PENDING |
IOS-XE logging config |
C9300-01 |
Core Switch |
10.50.1.11 |
PENDING |
IOS-XE logging config |
3560CX-01 |
Access Switch |
10.50.1.10 |
PENDING |
IOS logging config |
bind-01 |
DNS |
10.50.1.90 |
PENDING |
BIND query logging |
Servers (Wazuh Agent)
| Host | OS | IP | Status | Notes |
|---|---|---|---|---|
vault-01 |
Rocky Linux 9 |
10.50.1.60 |
PENDING |
PKI + SSH CA |
kvm-01 |
Arch Linux |
10.50.1.110 |
PENDING |
Primary hypervisor |
ipa-01 |
Rocky Linux 9 |
10.50.1.100 |
PENDING |
FreeIPA |
keycloak-01 |
Fedora 43 |
10.50.1.80 |
PENDING |
IdP |
k3s-master-01 |
Rocky Linux 9 |
10.50.1.120 |
PENDING |
k3s control plane |
home-dc01 |
Windows 2025 |
10.50.1.50 |
PENDING |
Domain Controller |
nas-01 |
Synology DSM |
10.50.1.70 |
PENDING |
Syslog (no agent) |
Phase 1: Fix Wazuh Indexing
|
This must be resolved before proceeding with other devices. Archives are being written to disk but NOT indexed in OpenSearch. |
1.1 Load Credentials + Cluster Health (Chained)
dsource d000 dev/observability && netapi wazuh health | jq -r '"Cluster: \(.cluster_name) | Status: \(.status) | Nodes: \(.number_of_nodes)"'
# EXPECTED: Cluster: wazuh-cluster | Status: green | Nodes: 1
1.2 Index Inventory with awk Formatting
netapi wazuh indices --raw | jq -r '.[] | "\(.index)\t\(.docs.count)\t\(.store.size)"' | \
awk -F'\t' 'BEGIN {printf "%-50s %10s %12s\n", "INDEX", "DOCS", "SIZE"; print "--------------------------------------------------------------------------------"}
/archives/ {printf "%-50s %10s %12s\n", $1, $2, $3}'
INDEX DOCS SIZE
--------------------------------------------------------------------------------
# PASTE ARCHIVE INDICES HERE (if empty = PROBLEM)
1.3 Process Check with awk Pivot
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- ps aux" | \
awk '/filebeat|logcollector|analysisd/ {printf "%-20s PID:%-6s CPU:%-5s MEM:%-5s\n", $11, $2, $3, $4}'
# EXPECTED PROCESSES:
# /var/ossec/bin/wazuh-logcollector PID:xxx CPU:x.x MEM:x.x
# /var/ossec/bin/wazuh-analysisd PID:xxx CPU:x.x MEM:x.x
# /usr/share/filebeat/bin/filebeat PID:xxx CPU:x.x MEM:x.x <-- CRITICAL
1.4 Filebeat Config Deep Dive
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- cat /etc/filebeat/filebeat.yml 2>/dev/null" | \
awk '/^filebeat\.inputs:/,/^[a-z]/' | head -30
# PASTE FILEBEAT INPUTS SECTION HERE
# Check output configuration (should point to wazuh-indexer)
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- cat /etc/filebeat/filebeat.yml 2>/dev/null" | \
awk '/^output\./,/^[a-z]/' | head -20
# PASTE OUTPUT SECTION HERE - VERIFY hosts: points to indexer
1.5 Filebeat Logs - Error Extraction
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- cat /var/log/filebeat/filebeat* 2>/dev/null" | \
grep -iE 'error|failed|refused|timeout' | tail -20 | \
awk '{gsub(/T/, " "); print}' | cut -c1-120
# ERRORS WILL APPEAR HERE
|
Common Filebeat Errors:
|
1.6 Manager Logs - jq Timeline
ssh k3s-master-01 "kubectl logs -n wazuh wazuh-manager-master-0 --since=1h 2>&1" | \
grep -iE 'indexer|opensearch|elastic|filebeat|archive' | \
awk '{print strftime("%H:%M:%S", systime()), $0}' | tail -30
# PASTE RELEVANT LOG LINES HERE
1.7 ossec.conf Archives Settings
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- cat /var/ossec/etc/ossec.conf" | \
awk '/<global>/,/<\/global>/' | grep -E 'logall|jsonout|archives'
# EXPECTED (for archives to work):
# <logall>yes</logall>
# <logall_json>yes</logall_json>
|
If Fix:
|
1.8 Direct Index Write Test
# Single command: write test doc + verify + cleanup
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-test/_doc" \
-H "Content-Type: application/json" \
-d "{\"timestamp\": \"$(date -Iseconds)\", \"test\": \"manual-write-$(hostname)\"}" | \
jq -r 'if .result == "created" then "✓ Index write successful (id: \(._id))" else "✗ FAILED: \(.error.type)" end'
# EXPECTED: ✓ Index write successful (id: xxxxx)
# Verify + cleanup in one chain
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD "https://10.50.1.131:9200/wazuh-archives-test/_count" | \
jq -r '"Test docs: \(.count)"' && \
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD -X DELETE "https://10.50.1.131:9200/wazuh-archives-test" | \
jq -r 'if .acknowledged then "✓ Test index cleaned up" else "⚠ Cleanup failed" end'
1.9 Index Templates - Deep Inspection
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
"https://10.50.1.131:9200/_index_template/wazuh*" | \
jq -r 'to_entries[] | "\(.key):\n patterns: \(.value.index_patterns | join(", "))\n priority: \(.value.priority)"'
# EXPECTED TEMPLATES:
# wazuh:
# patterns: wazuh-alerts-*, wazuh-archives-*, wazuh-statistics-*
# priority: 1
1.10 Full Diagnostic One-Liner
|
Run this single command for complete diagnostics: |
dsource d000 dev/observability && \
echo "=== CLUSTER ===" && netapi wazuh health | jq -r '.status' && \
echo "=== ARCHIVES ===" && netapi wazuh indices --raw 2>/dev/null | jq -r '.[] | select(.index | contains("archives")) | "\(.index): \(.docs.count) docs"' && \
echo "=== PROCESSES ===" && ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- pgrep -a filebeat" 2>/dev/null | head -1 && \
echo "=== ARCHIVE LOG ===" && ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- wc -l /var/ossec/logs/archives/archives.log 2>/dev/null" && \
echo "=== FILEBEAT ERRORS ===" && ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- grep -c -i error /var/log/filebeat/filebeat* 2>/dev/null || echo 0"
=== CLUSTER ===
# green/yellow/red
=== ARCHIVES ===
# wazuh-archives-4.x-2026.02.26: XXX docs (or empty if PROBLEM)
=== PROCESSES ===
# /usr/share/filebeat/bin/filebeat (or empty if NOT RUNNING)
=== ARCHIVE LOG ===
# XXXX /var/ossec/logs/archives/archives.log (lines in archive)
=== FILEBEAT ERRORS ===
# 0 (or error count)
Phase 2: Configure Network Devices
2.1 ISE Syslog Target
# Load network creds + check existing targets with formatted output
dsource d000 dev/network && \
netapi ise api-call ers GET '/config/externalSyslogTarget' | \
jq -r '.SearchResult.resources[] | "\(.name)\t\(.id)"' | \
awk -F'\t' 'BEGIN {printf "%-30s %s\n", "TARGET NAME", "UUID"} {printf "%-30s %s\n", $1, $2}'
TARGET NAME UUID
# EXISTING TARGETS LISTED HERE
# Create Wazuh target + extract Location header for verification
netapi ise api-call ers POST '/config/externalSyslogTarget' --data '{
"ExternalSyslogTarget": {
"name": "Wazuh-SIEM",
"description": "Wazuh SIEM syslog collector - k3s deployment",
"host": "10.50.1.134",
"port": 514,
"protocol": "UDP"
}
}' 2>&1 | jq -r 'if .SearchResult then "✓ Created: \(.SearchResult.resources[0].id)" elif .message then "✗ Error: \(.message)" else . end'
# EXPECTED: ✓ Created: <uuid>
# Verify creation + get full details
netapi ise api-call ers GET '/config/externalSyslogTarget' | \
jq -r '.SearchResult.resources[] | select(.name == "Wazuh-SIEM") | "Host: \(.host // "N/A")\nPort: \(.port // 514)\nProtocol: \(.protocol // "UDP")"'
2.2 WLC Syslog (netapi wlc config)
# Configure + save in single chain
netapi wlc config \
"logging host 10.50.1.134" \
"logging trap informational" \
"logging source-interface Loopback0" \
"logging origin-id hostname" \
--save && echo "✓ WLC syslog configured"
# PASTE CONFIG OUTPUT HERE
# Verify logging config with awk extraction
netapi wlc run "show logging" | \
awk '/Logging to/ || /Trap logging:/ || /host [0-9]/ {print}' | head -10
# EXPECTED:
# Logging to 10.50.1.134, Vty-VRF: 0
# Trap logging: level informational
2.3 Switch Syslog (C9300) - IOS-XE
# Configure via netapi ios config (chained)
netapi ios config C9300-01 \
"logging host 10.50.1.134" \
"logging trap informational" \
"logging source-interface Vlan1" \
"logging origin-id hostname" \
"logging on" \
--save && echo "✓ C9300 syslog configured"
# PASTE OUTPUT HERE
# Verify + parse with awk
netapi ios run C9300-01 "show logging" | \
awk '/Syslog logging:/ || /Trap logging:/ || /Logging to [0-9]/' | head -5
2.4 Switch Syslog (3560CX) - Classic IOS
# Configure via SSH heredoc (classic IOS doesn't have all netapi features)
ssh 3560CX-01 << 'EOF'
configure terminal
logging host 10.50.1.134
logging trap informational
logging on
logging buffered 16384
end
write memory
EOF
# Building configuration...
# [OK]
2.5 Bulk Verification - All Syslog Sources
|
Run after configuring all devices: |
# Multi-device syslog verification with jq + awk formatting
{
echo "DEVICE|TYPE|SYSLOG_TARGET|STATUS"
echo "pfSense|Firewall|$(netapi pfsense syslog show 2>/dev/null | awk '/Remote Server:/ {print $NF}')|$(netapi pfsense syslog show 2>/dev/null | awk '/Remote Logging:/ {print $NF}')"
echo "ISE-01|NAC|$(netapi ise api-call ers GET '/config/externalSyslogTarget' 2>/dev/null | jq -r '.SearchResult.resources[] | select(.name | contains("Wazuh")) | .host' | head -1)|Configured"
echo "WLC|Wireless|$(netapi wlc run 'show logging | inc Logging to' 2>/dev/null | awk '{print $3}' | cut -d, -f1)|$(netapi wlc run 'show logging | inc Trap logging' 2>/dev/null | awk '{print $NF}')"
} | awk -F'|' 'NR==1 {printf "%-15s %-12s %-20s %s\n", $1, $2, $3, $4} NR>1 {printf "%-15s %-12s %-20s %s\n", $1, $2, $3, $4}'
DEVICE TYPE SYSLOG_TARGET STATUS
pfSense Firewall 10.50.1.134 Enabled
ISE-01 NAC 10.50.1.134 Configured
WLC Wireless 10.50.1.134 informational
2.6 Live Syslog Reception Test
# Watch real-time syslog reception on Wazuh manager
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- tail -f /var/ossec/logs/archives/archives.log" | \
awk -F, '{gsub(/"/, ""); print strftime("%H:%M:%S"), $3, $4}' | head -20
# Count events per source in last 5 minutes
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- tail -1000 /var/ossec/logs/archives/archives.log" | \
jq -r '.location // "unknown"' 2>/dev/null | sort | uniq -c | sort -rn | head -10
# EXPECTED (if working):
# 523 10.50.1.1 (pfSense)
# 47 10.50.1.20 (ISE)
# 12 10.50.1.40 (WLC)
Phase 3: Deploy Wazuh Agents
3.1 Get Agent Registration Key
# Get registration password from Wazuh manager
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- cat /var/ossec/etc/authd.pass"
# PASTE OUTPUT HERE (save to gopass)
3.2 Linux Agent Installation (Rocky/Fedora)
# On target host
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import && chmod 644 /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" | tee /etc/apt/sources.list.d/wazuh.list
# For RPM-based (Rocky/Fedora)
rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH
cat > /etc/yum.repos.d/wazuh.repo << 'EOF'
[wazuh]
gpgcheck=1
gpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH
enabled=1
name=EL-$releasever - Wazuh
baseurl=https://packages.wazuh.com/4.x/yum/
protect=1
EOF
dnf install wazuh-agent -y
3.3 Agent Configuration
# Configure agent
cat > /var/ossec/etc/ossec.conf << 'EOF'
<ossec_config>
<client>
<server>
<address>{wazuh-manager-vip}</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
</client>
</ossec_config>
EOF
# Register and start
/var/ossec/bin/agent-auth -m {wazuh-manager-vip}
systemctl daemon-reload
systemctl enable wazuh-agent
systemctl start wazuh-agent
3.4 Arch Linux Agent
# Install from AUR
yay -S wazuh-agent
# Configure (same as above)
sudo vim /var/ossec/etc/ossec.conf
# Register
sudo /var/ossec/bin/agent-auth -m {wazuh-manager-vip}
sudo systemctl enable --now wazuh-agent
3.5 Windows Agent
# Download installer
Invoke-WebRequest -Uri https://packages.wazuh.com/4.x/windows/wazuh-agent-4.14.3-1.msi -OutFile wazuh-agent.msi
# Install with manager IP
msiexec.exe /i wazuh-agent.msi /q WAZUH_MANAGER="{wazuh-manager-vip}" WAZUH_REGISTRATION_SERVER="{wazuh-manager-vip}"
# Start service
NET START WazuhSvc
3.6 Verify Agent Registration
# Load creds + get agents with jq formatting
dsource d000 dev/observability && \
netapi wazuh agents --raw | \
jq -r '.[] | "\(.id)\t\(.name)\t\(.ip)\t\(.status)\t\(.os.name // "N/A")"' | \
awk -F'\t' 'BEGIN {printf "%-5s %-25s %-15s %-10s %s\n", "ID", "NAME", "IP", "STATUS", "OS"}
{printf "%-5s %-25s %-15s %-10s %s\n", $1, $2, $3, $4, $5}'
ID NAME IP STATUS OS
000 wazuh-manager-master-0 127.0.0.1 Active Rocky Linux 9
001 vault-01 10.50.1.60 Active Rocky Linux 9
002 kvm-01 10.50.1.99 Active Arch Linux
# ... more agents
# Quick health check: count by status
dsource d000 dev/observability && \
netapi wazuh agents --raw | jq -r '.[].status' | sort | uniq -c | \
awk '{printf "%s: %d agents\n", $2, $1}'
# Active: 8 agents
# Disconnected: 0 agents
Phase 4: Create Dashboards
4.1 pfSense Firewall Analysis
# Top blocked source IPs (last 24h) - OpenSearch aggregation + jq pivot
dsource d000 dev/observability && \
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-*/_search" \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"query": {"bool": {"must": [
{"match": {"full_log": "filterlog"}},
{"match": {"full_log": "block"}},
{"range": {"timestamp": {"gte": "now-24h"}}}
]}},
"aggs": {"top_sources": {"terms": {"field": "data.srcip", "size": 10}}}
}' | jq -r '.aggregations.top_sources.buckets[] | "\(.key)\t\(.doc_count)"' | \
awk -F'\t' 'BEGIN {printf "%-20s %10s\n", "SOURCE IP", "BLOCKS"} {printf "%-20s %10d\n", $1, $2}'
SOURCE IP BLOCKS
192.168.1.100 523
10.20.30.40 127
# ... top blocked IPs
# Top blocked destination ports
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-*/_search" \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"query": {"bool": {"must": [{"match": {"full_log": "block"}}]}},
"aggs": {"top_ports": {"terms": {"field": "data.dstport", "size": 10}}}
}' | jq -r '.aggregations.top_ports.buckets[] | "\(.key)\t\(.doc_count)"' | \
awk -F'\t' 'BEGIN {printf "%-10s %10s %s\n", "PORT", "COUNT", "SERVICE"}
{svc="unknown"; if($1==22) svc="SSH"; if($1==23) svc="Telnet"; if($1==80) svc="HTTP"; if($1==443) svc="HTTPS"; if($1==445) svc="SMB"; if($1==3389) svc="RDP"; printf "%-10s %10d %s\n", $1, $2, svc}'
4.2 ISE Authentication Analytics
# Auth success/failure ratio from archives
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-*/_search" \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"query": {"match": {"location": "10.50.1.20"}},
"aggs": {
"auth_results": {"terms": {"field": "data.outcome", "size": 5}}
}
}' | jq -r '.aggregations.auth_results.buckets[] | "\(.key): \(.doc_count)"'
Advanced Troubleshooting
OpenSearch Query DSL - Deep Queries
# Multi-field search with jq transformation pipeline
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-*/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {"bool": {"must": [
{"match": {"location": "10.50.1.1"}},
{"range": {"timestamp": {"gte": "now-1h"}}}
]}},
"size": 50,
"sort": [{"timestamp": "desc"}],
"_source": ["timestamp", "location", "full_log"]
}' | jq -r '.hits.hits[]._source | "\(.timestamp | split("T")[1] | split(".")[0])\t\(.location)\t\(.full_log | .[0:80])"' | \
awk -F'\t' 'BEGIN {printf "%-12s %-15s %s\n", "TIME", "SOURCE", "LOG (truncated)"} {printf "%-12s %-15s %s\n", $1, $2, $3}' | head -20
TIME SOURCE LOG (truncated)
12:34:56 10.50.1.1 Feb 26 12:34:56 filterlog[50531]: 65,,,12004,ixl0,match,b...
# Event count per hour (time histogram) - great for dashboards
curl -sk -u admin:$WAZUH_INDEXER_PASSWORD \
-X POST "https://10.50.1.131:9200/wazuh-archives-*/_search" \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"query": {"range": {"timestamp": {"gte": "now-24h"}}},
"aggs": {"events_per_hour": {"date_histogram": {"field": "timestamp", "calendar_interval": "hour"}}}
}' | jq -r '.aggregations.events_per_hour.buckets[] | "\(.key_as_string | split("T")[1] | split(":")[0]):00\t\(.doc_count)"' | \
awk -F'\t' 'BEGIN {printf "%-8s %10s %s\n", "HOUR", "EVENTS", "HISTOGRAM"} {bar=""; for(i=0;i<$2/100;i++) bar=bar"█"; printf "%-8s %10d %s\n", $1, $2, bar}'
HOUR EVENTS HISTOGRAM
00:00 523 █████
01:00 412 ████
02:00 234 ██
# ... hourly distribution
Wazuh API - Token Chain Pattern
# Get token + use immediately (single chain, no temp vars)
curl -sk -u wazuh-wui:$WAZUH_API_PASSWORD \
-X POST "https://10.50.1.134:55000/security/user/authenticate" | \
jq -r '.data.token' | \
xargs -I{} curl -sk -H "Authorization: Bearer {}" \
"https://10.50.1.134:55000/agents?select=id,name,status,ip" | \
jq -r '.data.affected_items[] | "\(.id)\t\(.name)\t\(.status)\t\(.ip)"' | \
awk -F'\t' 'BEGIN {printf "%-5s %-25s %-12s %s\n", "ID", "NAME", "STATUS", "IP"}
{printf "%-5s %-25s %-12s %s\n", $1, $2, $3, $4}'
# Get agent summary stats with jq aggregation
curl -sk -u wazuh-wui:$WAZUH_API_PASSWORD \
-X POST "https://10.50.1.134:55000/security/user/authenticate" | \
jq -r '.data.token' | \
xargs -I{} curl -sk -H "Authorization: Bearer {}" \
"https://10.50.1.134:55000/agents/summary/status" | \
jq -r '.data | "Total: \(.total) | Active: \(.active) | Disconnected: \(.disconnected) | Pending: \(.pending)"'
Restart + Monitor Pattern
# Restart + watch rollout in single chain
ssh k3s-master-01 "kubectl rollout restart deployment/wazuh-manager -n wazuh && \
kubectl rollout status deployment/wazuh-manager -n wazuh --timeout=120s && \
echo '✓ Manager restarted successfully' || echo '✗ Rollout failed'"
# Restart all Wazuh components + verify
ssh k3s-master-01 "for comp in wazuh-manager wazuh-dashboard; do \
kubectl rollout restart deployment/\$comp -n wazuh 2>/dev/null; \
done && \
kubectl rollout restart statefulset/wazuh-indexer -n wazuh && \
sleep 30 && \
kubectl get pods -n wazuh -o custom-columns='NAME:.metadata.name,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount'"
Archive Log Analysis
# Event rate calculation (events per minute)
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- sh -c '\
START=\$(wc -l < /var/ossec/logs/archives/archives.log); \
sleep 60; \
END=\$(wc -l < /var/ossec/logs/archives/archives.log); \
echo \"Rate: \$((END-START)) events/minute\"'"
# Top event types in last 1000 lines (awk frequency analysis)
ssh k3s-master-01 "kubectl exec -n wazuh wazuh-manager-master-0 -- tail -1000 /var/ossec/logs/archives/archives.log" | \
jq -r '.rule.description // .decoder.name // "unknown"' 2>/dev/null | \
sort | uniq -c | sort -rn | head -10 | \
awk '{count=$1; $1=""; printf "%6d %s\n", count, $0}'
# 523 pfSense: Firewall block
# 127 File integrity monitoring
# 45 SSH session opened
Validation Checklist
| # | Check | Status |
|---|---|---|
1 |
OpenSearch cluster health = green |
[ ] |
2 |
Archives index exists for today’s date |
[ ] |
3 |
pfSense logs visible in archives |
[ ] |
4 |
All network devices sending syslog |
[ ] |
5 |
All servers have Wazuh agent installed |
[ ] |
6 |
All agents showing "Active" status |
[ ] |
7 |
Dashboards created and functional |
[ ] |
8 |
Alert rules triggering correctly |
[ ] |
Quick Reference - jq + awk Patterns
|
The jq + awk combination is extremely powerful for security operations. jq handles JSON parsing, awk handles tabular formatting. Chain them for production-grade output. |
Pattern 1: API → jq → awk Table
# Generic pattern: API call | jq extract | awk format
<api_call> | jq -r '.items[] | "\(.field1)\t\(.field2)\t\(.field3)"' | \
awk -F'\t' 'BEGIN {printf "%-20s %-15s %s\n", "COL1", "COL2", "COL3"}
{printf "%-20s %-15s %s\n", $1, $2, $3}'
Pattern 2: Conditional jq Output
# Success/failure with emoji indicators
<command> | jq -r 'if .status == "success" then "✓ \(.message)" else "✗ \(.error)" end'
Pattern 3: awk Histogram
# Turn counts into visual bars
<data> | awk '{bar=""; for(i=0;i<$1/10;i++) bar=bar"█"; printf "%6d %s %s\n", $1, bar, $2}'
Pattern 4: jq Aggregation
# Group + count with jq
<json> | jq -r 'group_by(.field) | .[] | {key: .[0].field, count: length} | "\(.key): \(.count)"'
Pattern 5: xargs Chain
# Use result of first command in second (no temp vars)
<get_id_command> | jq -r '.id' | xargs -I{} <second_command_using_{}>
Pattern 6: Multi-Source Data
# Combine multiple sources with process substitution
paste <(cmd1 | jq -r '.field1') <(cmd2 | jq -r '.field2') | \
awk '{printf "%-20s %s\n", $1, $2}'
Pattern 7: awk Service Lookup
# Inline port→service mapping
awk '{svc="unknown"; if($1==22)svc="SSH"; if($1==443)svc="HTTPS"; if($1==3389)svc="RDP"; print $0, svc}'
Pattern 8: jq Default Values
# Handle null/missing fields gracefully
jq -r '.field // "N/A"' # Default string
jq -r '.count // 0' # Default number
jq -r '.items // empty' # Skip if missing
One-Liner Cheatsheet
| Operation | Command |
|---|---|
Load Wazuh creds |
|
Cluster health (green/yellow/red) |
|
Archive index count |
|
Events per source |
|
Agent status summary |
|
Top 5 rule IDs |
|
Test OpenSearch write |
|
Watch syslog live |
|
pfSense syslog status |
|
Restart Wazuh pods |
|