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

  1. Line 2: = should be == (comparison, not assignment)

  2. Line 4: Missing then after condition

  3. 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

  1. Practice daily - Run these exercises until fluent

  2. Test with real logs - Get sample ISE/FTD exports

  3. Build production transforms - Apply to CHLA pipeline

  4. 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