Hands-On Lab
Practice Monad transforms with synthetic log data. These exercises build from basic to advanced, preparing you for production deployment.
Prerequisites
# Load Monad credentials
dsource d000 lab/app
# Verify environment
monad_check_env
monad_summary
Exercise 1: Basic Field Operations
Objective
Practice field access, addition, and renaming.
Sample Log
{
"timestamp": "2026-03-15T14:30:45Z",
"source_ip": "192.168.1.100",
"dest_ip": "10.50.1.20",
"user": "jdoe",
"action": "login",
"status": "success"
}
Tasks
1.1 Add environment tag:
. + {"environment": "production"}
1.2 Rename source_ip to src.ip:
.src = {ip: .source_ip} | del(.source_ip)
1.3 Add timestamp if missing:
. + {"ingested_at": (now | todate)}
Verify
echo '{"source_ip":"192.168.1.100","user":"jdoe"}' | jq '
. + {
"environment": "production",
"ingested_at": (now | todate)
} |
.src = {ip: .source_ip} |
del(.source_ip)
'
Exercise 2: Conditional Routing
Objective
Route logs based on field values.
Sample Logs
{"severity": "critical", "event": "auth_failure", "user": "admin"}
{"severity": "info", "event": "login", "user": "jdoe"}
{"severity": "high", "event": "config_change", "user": "netadmin"}
{"severity": "low", "event": "heartbeat", "user": "system"}
Tasks
2.1 Route by severity:
if .severity | IN("critical", "high") then
. + {"route": "sentinel"}
else
. + {"route": "s3"}
end
2.2 Route by event type:
if .event | IN("auth_failure", "config_change", "intrusion") then
. + {"route": "sentinel", "priority": "high"}
else
. + {"route": "s3", "priority": "low"}
end
2.3 Combined routing:
if (.severity | IN("critical", "high")) or
(.event | IN("auth_failure", "config_change")) then
. + {"route": "sentinel"}
else
. + {"route": "s3"}
end
Verify
cat << 'EOF' | jq -c '.' | while read line; do
echo "$line" | jq '
if (.severity | IN("critical", "high")) or
(.event | IN("auth_failure", "config_change")) then
. + {"route": "sentinel"}
else
. + {"route": "s3"}
end
' | jq -c '{event, severity, route}'
done
{"severity": "critical", "event": "auth_failure"}
{"severity": "info", "event": "login"}
{"severity": "high", "event": "config_change"}
EOF
Exercise 3: MITRE ATT&CK Tagging
Objective
Add MITRE technique IDs based on event types.
Sample ISE Logs
{"event_type": "auth_failure", "user": "jdoe", "ise.auth_result": "FAILED"}
{"event_type": "posture_failure", "user": "jsmith", "ise.posture_status": "NonCompliant"}
{"event_type": "auth_success", "user": "admin", "ise.auth_result": "PASSED"}
Tasks
3.1 Single technique mapping:
if .event_type == "auth_failure" then
. + {"mitre_technique": "T1078", "mitre_name": "Valid Accounts"}
else
.
end
3.2 Full technique mapping:
(
{
"auth_failure": {"id": "T1078", "name": "Valid Accounts", "tactic": "initial_access"},
"posture_failure": {"id": "T1190", "name": "Exploit Public-Facing", "tactic": "initial_access"},
"brute_force": {"id": "T1110", "name": "Brute Force", "tactic": "credential_access"},
"command_exec": {"id": "T1059", "name": "Command Execution", "tactic": "execution"}
}[.event_type] // null
) as $mitre |
if $mitre then
. + {"mitre": $mitre}
else
.
end
Verify
echo '{"event_type": "auth_failure", "user": "jdoe"}' | jq '
(
{
"auth_failure": {"id": "T1078", "name": "Valid Accounts"}
}[.event_type] // null
) as $mitre |
if $mitre then . + {"mitre": $mitre} else . end
'
Exercise 4: Drop Low-Value Records
Objective
Filter out high-volume, low-value logs.
Sample Logs
{"severity": "info", "event": "heartbeat", "source": "monitor"}
{"severity": "critical", "event": "intrusion", "source": "ftd"}
{"severity": "debug", "event": "trace", "source": "app"}
{"severity": "high", "event": "auth_failure", "source": "ise"}
Tasks
4.1 Drop info and debug:
if .severity | IN("info", "debug", "trace") then
empty # Drops the record
else
.
end
4.2 Drop specific event types:
if .event | IN("heartbeat", "keepalive", "health_check") then
empty
else
.
end
4.3 Combined filter:
if (.severity | IN("info", "debug")) or
(.event | IN("heartbeat", "keepalive")) then
empty
else
.
end
Verify
cat << 'EOF' | jq -s '[.[] | select(.severity | IN("info", "debug") | not)]'
{"severity": "info", "event": "heartbeat"}
{"severity": "critical", "event": "intrusion"}
{"severity": "debug", "event": "trace"}
{"severity": "high", "event": "auth_failure"}
EOF
Expected output: Only critical and high severity records.
Exercise 5: ISE RADIUS Transform
Objective
Build production ISE RADIUS routing transform.
Sample ISE Log
{
"timestamp": "2026-03-15T14:30:45Z",
"ise": {
"auth_result": "FAILED",
"failure_reason": "Certificate validation failed",
"policy_set": "Wired_802.1X_Closed",
"authorization_profile": "DENY_ACCESS"
},
"user": {
"name": "jdoe",
"domain": "CHLA"
},
"endpoint": {
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "10.50.10.100"
}
}
Task
Build complete transform that: 1. Routes auth failures to Sentinel 2. Adds MITRE T1078 tag 3. Normalizes field names 4. Adds ingestion timestamp 5. Routes success to S3
Solution
# ISE RADIUS Production Transform
(
# Determine routing
if (.ise.auth_result == "FAILED" or
.ise.posture_status == "NonCompliant" or
.ise.authorization_profile == "DENY_ACCESS") then
{
"route": "sentinel",
"category": "ise_security",
"mitre": {"id": "T1078", "name": "Valid Accounts"}
}
else
{
"route": "s3_archive",
"category": "ise_accounting"
}
end
) as $routing |
# Build normalized output
{
"timestamp": .timestamp,
"ingested_at": (now | todate),
"log_source": "ise_radius",
"authentication": {
"status": .ise.auth_result,
"failure_reason": .ise.failure_reason,
"policy": .ise.policy_set,
"profile": .ise.authorization_profile
},
"user": .user,
"endpoint": .endpoint
} + $routing
Verify
cat << 'EOF' | jq '
# Your transform here
if .ise.auth_result == "FAILED" then
. + {"route": "sentinel", "mitre": "T1078"}
else
. + {"route": "s3"}
end
'
{
"ise": {"auth_result": "FAILED"},
"user": {"name": "jdoe"}
}
EOF
Exercise 6: Multi-Source Router
Objective
Build a single transform that handles ISE, FTD, and VPN logs.
Task
Create transform that: 1. Detects log source type 2. Applies source-specific routing 3. Adds appropriate MITRE tags 4. Adds common fields (ingested_at, log_source)
Solution
# Multi-Source Router
(
# Detect source
if .ise.auth_result then "ise_radius"
elif .tacacs then "ise_tacacs"
elif .vpn then "asa_vpn"
elif .action and .src and .dst then "ftd"
else "unknown"
end
) as $source |
# Route based on source
(
if $source == "ise_radius" then
if .ise.auth_result == "FAILED" then
{"route": "sentinel", "mitre": "T1078"}
else
{"route": "s3"}
end
elif $source == "ftd" then
if .action == "deny" or .threat then
{"route": "sentinel", "mitre": (if .threat then "T1071" else null end)}
else
{"route": "s3"}
end
elif $source == "asa_vpn" then
if .vpn.auth_result == "FAILED" then
{"route": "sentinel", "mitre": "T1133"}
else
{"route": "s3"}
end
else
{"route": "s3"}
end
) as $routing |
. + $routing + {
"log_source": $source,
"ingested_at": (now | todate)
}
Exercise 7: Volume Analysis
Objective
Analyze routing decisions to estimate volume reduction.
Task
Given these log counts, calculate Sentinel vs S3 volumes:
Total ISE RADIUS: 1000
- auth_result=FAILED: 150
- auth_result=PASSED: 850
Total FTD: 5000
- action=deny: 500
- action=permit: 4500
Calculation
ISE → Sentinel: 150 (15%)
ISE → S3: 850 (85%)
FTD → Sentinel: 500 (10%)
FTD → S3: 4500 (90%)
Total → Sentinel: 650 (10.8%)
Total → S3: 5350 (89.2%)
Volume reduction: 89.2%
Exercise 8: Debug a Transform
Objective
Find and fix bugs in a transform.
Buggy Transform
# Find the bugs
if .severity = "critical" then
. + {"route": "sentinel"}
elif .event_type = "auth_failure"
. + {"route": "sentinel", "mitre": "T1078"}
else
{"route": "s3"}
end
Bugs
-
Line 2:
=should be==(comparison, not assignment) -
Line 4: Missing
thenafter condition -
Line 7: Returns only route, loses original record
Fixed Transform
if .severity == "critical" then
. + {"route": "sentinel"}
elif .event_type == "auth_failure" then
. + {"route": "sentinel", "mitre": "T1078"}
else
. + {"route": "s3"}
end
Self-Assessment Checklist
Rate yourself on each skill (1-5):
| Skill | Rating |
|---|---|
Add static fields with jq |
|
Conditional routing (if-then-else) |
|
MITRE technique mapping |
|
Drop records (filtering) |
|
Field renaming/normalization |
|
Multi-source detection |
|
Debug jq transforms |
|
Estimate volume reduction |
Target: All skills at 4+ before production deployment.
Next Steps
-
Practice daily - Run these exercises until fluent
-
Test with real logs - Get sample ISE/FTD exports
-
Build production transforms - Apply to CHLA pipeline
-
Monitor and tune - Adjust based on actual volumes
Quick Reference Card
# Add field
. + {"key": "value"}
# Conditional
if .field == "value" then ... else ... end
# Drop record
empty
# Delete field
del(.field)
# Rename field
.new_field = .old_field | del(.old_field)
# Check membership
.field | IN("a", "b", "c")
# Safe access
.field // "default"
# Timestamp
now | todate