Routing Patterns

Production routing patterns for the QRadar → Sentinel migration. These patterns implement the CHLA filtering strategy to reduce Sentinel costs by 50-70% while maintaining compliance.

Routing Strategy

Destination Matrix

Destination What Goes There Volume Cost Impact

Sentinel

Critical security events

~20-30% of logs

Paid per GB

S3 Archive

Compliance retention

~70-80% of logs

Cheap storage

Decision Logic

IF security_relevant THEN
  route → Sentinel
ELSE
  route → S3 (compliance archive)
END

ISE RADIUS Routing

Critical → Sentinel

Condition Description MITRE

ise.auth_result == "FAILED"

Authentication failures

T1078

ise.posture_status == "NonCompliant"

Posture check failures

T1190

ise.authorization_profile == "DENY_ACCESS"

Authorization denials

T1078

ise.coa_action == "reauth"

Change of Authorization

T1078

jq Transform:

# ISE RADIUS - Route auth failures to Sentinel
if (.ise.auth_result == "FAILED" or
    .ise.posture_status == "NonCompliant" or
    .ise.authorization_profile == "DENY_ACCESS" or
    .ise.coa_action == "reauth") then
  . + {
    "route": "sentinel",
    "category": "ise_security",
    "mitre_technique": "T1078"
  }
else
  . + {
    "route": "s3_archive",
    "category": "ise_accounting"
  }
end

Archive → S3

Condition Description

ise.auth_result == "PASSED"

Successful authentications

event_type == "accounting"

Session accounting records

event_type == "keepalive"

Periodic health checks

ISE TACACS Routing

Critical → Sentinel

Condition Description MITRE

tacacs.command_type == "config"

Configuration changes

T1059

tacacs.privilege_level >= 7

Elevated privilege commands

T1059

tacacs.auth_result == "FAILED"

Admin auth failures

T1078

jq Transform:

# ISE TACACS - Route admin actions to Sentinel
if (.tacacs.command_type == "config" or
    .tacacs.privilege_level >= 7 or
    .tacacs.auth_result == "FAILED") then
  . + {
    "route": "sentinel",
    "category": "tacacs_admin",
    "mitre_technique": "T1059"
  }
else
  . + {
    "route": "s3_archive",
    "category": "tacacs_accounting"
  }
end

Archive → S3

Condition Description

tacacs.command_type == "show"

Read-only commands

event_type == "accounting_start"

Session start records

event_type == "accounting_stop"

Session stop records

FTD Firewall Routing

Critical → Sentinel

Condition Description MITRE

action == "deny"

Blocked connections

T1071

action == "intrusion_detected"

IDS/IPS alerts

T1190

action == "malware_detected"

Malware detections

T1204

threat.severity IN ("critical", "high")

High severity threats

varies

jq Transform:

# FTD Firewall - Route threats to Sentinel
if (.action == "deny" or
    .action == "intrusion_detected" or
    .action == "malware_detected" or
    (.threat.severity | IN("critical", "high"))) then

  # Determine MITRE technique
  (if .action == "intrusion_detected" then "T1190"
   elif .action == "malware_detected" then "T1204"
   elif .threat.signature then "T1071"
   else null end) as $mitre |

  . + {
    "route": "sentinel",
    "category": "firewall_security",
    "mitre_technique": $mitre
  }
else
  . + {
    "route": "s3_archive",
    "category": "firewall_flow"
  }
end

Archive → S3

Condition Description

action == "permit" or "allow"

Permitted traffic (flow logs)

event_type == "connection_closed"

Connection terminations

event_type == "flow_statistics"

Traffic statistics

ASA VPN Routing

Critical → Sentinel

Condition Description MITRE

vpn.auth_result == "FAILED"

VPN auth failures

T1078

vpn.posture_status == "NonCompliant"

Client posture failures

T1190

vpn.connection_type == "admin"

Admin VPN sessions

T1133

jq Transform:

# ASA VPN - Route auth issues to Sentinel
if (.vpn.auth_result == "FAILED" or
    .vpn.posture_status == "NonCompliant" or
    .vpn.connection_type == "admin") then
  . + {
    "route": "sentinel",
    "category": "vpn_security",
    "mitre_technique": "T1133"
  }
else
  . + {
    "route": "s3_archive",
    "category": "vpn_session"
  }
end

Network Device Routing

Critical → Sentinel

Condition Description MITRE

event_type == "config_change"

Configuration changes

T1059

event_type == "interface_down"

Interface failures

-

severity IN ("emergency", "alert", "critical")

High severity events

-

jq Transform:

# Network Devices - Route config changes and alerts
if (.event_type == "config_change" or
    .event_type == "interface_down" or
    (.severity | IN("emergency", "alert", "critical"))) then
  . + {
    "route": "sentinel",
    "category": "network_alert"
  }
else
  . + {
    "route": "s3_archive",
    "category": "network_info"
  }
end

Combined Multi-Source Transform

Master routing transform for all sources:

# CHLA Master Routing Transform
# Handles: ISE RADIUS, ISE TACACS, FTD, ASA VPN, Network

# Detect source type
(
  if .ise.auth_result then "ise_radius"
  elif .tacacs then "ise_tacacs"
  elif .vpn then "asa_vpn"
  elif .action and (.src.zone or .dst.zone) then "ftd"
  elif .device.type | IN("switch", "router", "wlc") then "network"
  else "unknown"
  end
) as $source |

# Determine routing based on source and conditions
(
  if $source == "ise_radius" then
    if (.ise.auth_result == "FAILED" or .ise.posture_status == "NonCompliant") then
      {"route": "sentinel", "mitre": "T1078"}
    else
      {"route": "s3_archive"}
    end
  elif $source == "ise_tacacs" then
    if (.tacacs.command_type == "config" or .tacacs.auth_result == "FAILED") then
      {"route": "sentinel", "mitre": "T1059"}
    else
      {"route": "s3_archive"}
    end
  elif $source == "ftd" then
    if (.action == "deny" or .threat.signature) then
      {"route": "sentinel", "mitre": (if .threat then "T1071" else null end)}
    else
      {"route": "s3_archive"}
    end
  elif $source == "asa_vpn" then
    if (.vpn.auth_result == "FAILED") then
      {"route": "sentinel", "mitre": "T1133"}
    else
      {"route": "s3_archive"}
    end
  elif $source == "network" then
    if (.event_type == "config_change" or .severity | IN("emergency", "alert", "critical")) then
      {"route": "sentinel"}
    else
      {"route": "s3_archive"}
    end
  else
    {"route": "s3_archive"}
  end
) as $routing |

# Apply routing
. + $routing + {"log_source": $source}

Volume Estimates

Source Total Volume → Sentinel → S3

ISE RADIUS

10 GB/day

~2 GB (20%)

~8 GB (80%)

ISE TACACS

1 GB/day

~0.3 GB (30%)

~0.7 GB (70%)

FTD

15 GB/day

~3 GB (20%)

~12 GB (80%)

ASA VPN

2 GB/day

~0.4 GB (20%)

~1.6 GB (80%)

Network

5 GB/day

~0.5 GB (10%)

~4.5 GB (90%)

TOTAL

33 GB/day

~6 GB (18%)

~27 GB (82%)

Cost Savings: ~80% reduction in Sentinel ingestion costs.

Compliance Considerations

S3 Archive Requirements

Requirement Implementation

Retention

7 years (HIPAA)

Encryption

SSE-S3 or SSE-KMS

Access Control

IAM policies, no public access

Audit Trail

S3 access logging enabled

Sentinel Data Needs

All security-relevant events must reach Sentinel for:

  • Real-time alerting

  • Threat hunting

  • Incident response

  • Compliance reporting

Testing Routing

Validate with Synthetic Logs

# Generate test logs and verify routing
./examples/monad/testing/synthetic-logs.sh <pipeline-id> 10

# Check pipeline logs
monad_logs_pipeline <pipeline-id> | jq '.logs[-10:] | .[] | {route, category, mitre_technique}'

Spot Check Production

# Sample recent logs
monad_logs_pipeline <pipeline-id> | jq '
  .logs |
  group_by(.route) |
  map({route: .[0].route, count: length})
'

Key Takeaways

  1. Security events → Sentinel (auth failures, threats, config changes)

  2. Everything else → S3 (successful auths, flows, accounting)

  3. MITRE tagging enables hunting - Add technique IDs

  4. Volume reduction 70-80% - Major cost savings

  5. Compliance retained - S3 archive meets HIPAA

  6. Test before production - Validate routing logic

Next Module

Hands-On Lab - Practice with synthetic logs.