Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7dbf117
Add Cisco ISE syslog parsing and OCSF mapping
zedoraps Jun 22, 2026
164e254
Reassemble multi-segment ISE messages in syslog example
zedoraps Jun 22, 2026
2073391
Drop logger_host arg from ISE mapper
zedoraps Jun 22, 2026
3a33f53
Test reassembly of interleaved multi-segment ISE messages
zedoraps Jun 22, 2026
b49ee79
Handle incomplete ISE reassembly
zedoraps Jun 22, 2026
f641159
Trim Cisco ISE changelog
zedoraps Jun 22, 2026
83dfc47
Fix Cisco ISE syslog listener example
zedoraps Jun 22, 2026
c6f368f
Use move in Cisco ISE mappers
zedoraps Jun 23, 2026
fca604d
Avoid needless ISE alias temporaries
zedoraps Jun 23, 2026
429c965
Move consumed ISE mapper fields
zedoraps Jun 23, 2026
a968d18
Trim Cisco ISE comments
zedoraps Jun 23, 2026
e5849c6
Tighten Cisco ISE release notes
zedoraps Jun 23, 2026
16a5ca9
Bound ISE reassembly grouping by window
zedoraps Jun 23, 2026
0ed8118
Cover ISE segments across windows
zedoraps Jun 23, 2026
31e65b1
Harden ISE reassembly edge cases
zedoraps Jun 23, 2026
3a20d44
Reassemble ISE segments across boundaries
zedoraps Jun 23, 2026
19f0de9
Use deduplicate for ISE segments
zedoraps Jun 23, 2026
27a0a46
Rename ISE emit window guard
zedoraps Jun 23, 2026
81df750
Keep reused single ISE ids separate
zedoraps Jun 24, 2026
65d153d
Anonymize reused ISE id fixture
zedoraps Jun 24, 2026
2de1951
Map ISE selected access service
zedoraps Jun 24, 2026
d886fea
Support category-less ISE OCSF dispatch
zedoraps Jun 24, 2026
23d64fa
Require ISE category for OCSF dispatch
zedoraps Jun 24, 2026
8d3184c
Lower default ISE reassembly span
zedoraps Jun 25, 2026
7cbcfa0
Handle late ISE header segments
zedoraps Jun 26, 2026
972da21
Tolerate out-of-order ISE segments
zedoraps Jun 26, 2026
51cc45a
Move reassembled ISE message
zedoraps Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cisco/changelog/unreleased/cisco-ise-ocsf-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: Cisco ISE syslog parsing and OCSF mapping
type: feature
authors:
- zedoraps
- claude
prs:
- 161
created: 2026-06-18T00:00:00Z
---

The `cisco` package now parses Cisco Identity Services Engine (ISE) syslog and
maps parsed ISE events to OCSF.

Use `cisco::ise::parse` to normalize ISE message bodies and
`cisco::ise::reassemble` before parsing when ISE splits a logical message into
numbered syslog segments.

The `cisco::ise::ocsf::map` operator maps Passed Authentications and Failed
Attempts to OCSF Authentication (3002), RADIUS Accounting to OCSF Network
Activity (4001), and all other ISE categories to OCSF Base Events. Incomplete
messages are kept as `cisco.ise.incomplete` Base Events with truncation
metadata instead of being dropped or emitted as complete events.
25 changes: 25 additions & 0 deletions cisco/examples/ise-syslog-to-ocsf.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Cisco ISE syslog β†’ OCSF
description: |
Listens for Cisco ISE remote-target syslog over TCP, reassembles segmented
messages, and maps parsed events to OCSF.
---

accept_tcp "0.0.0.0:514" {
read_syslog
}
event_month = timestamp.parse_time("%b %e %H:%M:%S").month()
year = now().year()
if event_month == 12 and now().month() == 1 {
year = year - 1
} else if event_month == 1 and now().month() == 12 {
year = year + 1
}
timestamp = (year.string() + " " + timestamp).parse_time("%Y %b %e %H:%M:%S")
drop event_month, year
cisco::ise::reassemble message=content, on=timestamp
cisco::ise::parse
cisco::ise::ocsf::map
ocsf::derive
ocsf::cast
publish "ocsf"
12 changes: 12 additions & 0 deletions cisco/examples/ise-to-ocsf.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: Cisco ISE β†’ OCSF
description: |
Maps parsed Cisco ISE events from the `cisco` topic to OCSF.
---

subscribe "cisco"
where @name.starts_with("cisco.ise.")
cisco::ise::ocsf::map
ocsf::derive
ocsf::cast
publish "ocsf"
20 changes: 20 additions & 0 deletions cisco/operators/ise/ocsf/base.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
description: Cisco ISE unsupported records β†’ OCSF Base Event
args:
named:
- name: event
description: The field that holds the event to map.
type: field
---

@name = "ocsf.base_event"
$event.ocsf.category_uid = 0
$event.ocsf.class_uid = 0
$event.ocsf.activity_id = 0
$event.ocsf.type_uid = 0

if $event.ise.timestamp? != null {
$event.ocsf.time = move $event.ise.timestamp
}
drop $event.ise.timestamp?
$event.ocsf.message = move $event.ise.message_text?
130 changes: 130 additions & 0 deletions cisco/operators/ise/ocsf/events/authentication.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
description: Cisco ISE RADIUS/TACACS authentications β†’ OCSF Authentication
args:
named:
- name: event
description: The field that holds the event to map.
type: field
---

@name = "ocsf.authentication"
$event.ocsf.category_uid = 3
$event.ocsf.class_uid = 3002
$event.ocsf.activity_id = 1
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

if $event.ise.timestamp? != null {
$event.ocsf.time = move $event.ise.timestamp
}
drop $event.ise.timestamp?
$event.ocsf.message = move $event.ise.message_text?
drop $event.ise.attributes["Event-Timestamp"]?

if $event.ise.message_code >= 5400 and $event.ise.message_code < 5500 {
$event.ocsf.status_id = 2
$event.ocsf.status = "Failure"
} else {
$event.ocsf.status_id = 1
$event.ocsf.status = "Success"
}
if $event.ise.attributes.FailureReason? != null {
$event.ocsf.status_detail = move $event.ise.attributes.FailureReason
}

if ($event.ise.attributes.UserName? else $event.ise.attributes["User-Name"]?) != null {
$event.ocsf.user.name = move $event.ise.attributes.UserName? else move $event.ise.attributes["User-Name"]?
}
drop $event.ise.attributes.UserName?, $event.ise.attributes["User-Name"]?
// ISE `UserType` is `User` (a person) or `Host` (a device identity, e.g. MAB).
// `Host` has no OCSF user type, so it maps to Other with the raw value.
user_type = move $event.ise.attributes.UserType?
if user_type == "User" {
$event.ocsf.user.type_id = 1
$event.ocsf.user.type = "User"
} else if user_type != null {
$event.ocsf.user.type_id = 99
$event.ocsf.user.type = user_type
}
drop user_type

if $event.ise.attributes.AcsSessionID? != null {
$event.ocsf.session = {uid: move $event.ise.attributes.AcsSessionID}
}
if $event.ise.attributes.SelectedAccessService? != null {
$event.ocsf.service.name = move $event.ise.attributes.SelectedAccessService
}

if $event.ise.attributes.DestinationIPAddress? != null {
$event.ocsf.dst_endpoint.ip = ip(move $event.ise.attributes.DestinationIPAddress)
}
if $event.ise.attributes.DestinationPort? != null {
$event.ocsf.dst_endpoint.port = int(move $event.ise.attributes.DestinationPort)
}

src_ip = move $event.ise.attributes["Framed-IP-Address"]? else move $event.ise.attributes.IpAddress?
if src_ip != null {
$event.ocsf.src_endpoint.ip = ip(src_ip)
}
drop src_ip, $event.ise.attributes["Framed-IP-Address"]?, $event.ise.attributes.IpAddress?
src_mac = move $event.ise.attributes["Calling-Station-ID"]? else move $event.ise.attributes.EndPointMACAddress?
if src_mac != null {
$event.ocsf.src_endpoint.mac = src_mac
}
drop src_mac, $event.ise.attributes["Calling-Station-ID"]?, $event.ise.attributes.EndPointMACAddress?

device_ip = move $event.ise.attributes["Device IP Address"]? else move $event.ise.attributes["NAS-IP-Address"]?
if device_ip != null {
$event.ocsf.device.ip = ip(device_ip)
}
drop device_ip, $event.ise.attributes["Device IP Address"]?, $event.ise.attributes["NAS-IP-Address"]?
if $event.ise.attributes.NetworkDeviceName? != null {
$event.ocsf.device.hostname = move $event.ise.attributes.NetworkDeviceName
}

protocol = move $event.ise.attributes.Protocol?
if protocol == "Radius" {
$event.ocsf.auth_protocol_id = 10
$event.ocsf.auth_protocol = "RADIUS"
} else if protocol != null {
$event.ocsf.auth_protocol_id = 99
$event.ocsf.auth_protocol = protocol
}
drop protocol

$event.ocsf.is_remote = true

// ISE exposes identity groups across user and endpoint fields.
identity_groups = [move $event.ise.attributes.IdentityGroup?, move $event.ise.attributes.HostIdentityGroup?, move $event.ise.attributes.Name?].where(g => g != null).distinct()
if identity_groups.length() > 0 {
$event.ocsf.user.groups = identity_groups.map(name => {name: name})
}
drop identity_groups
if $event.ise.attributes.CPMSessionID? != null {
$event.ocsf.session.uid_alt = move $event.ise.attributes.CPMSessionID
}
if $event.ise.attributes["Model Name"]? != null {
$event.ocsf.device.model = move $event.ise.attributes["Model Name"]
}

// Authorization is separate from authentication: a successful authentication can
// still be denied access.
authz_profile = move $event.ise.attributes.SelectedAuthorizationProfiles?
if authz_profile != null {
$event.ocsf.metadata.profiles = $event.ocsf.metadata.profiles.concatenate(["security_control"])
if authz_profile == "DenyAccess" {
$event.ocsf.disposition_id = 2
$event.ocsf.disposition = "Blocked"
$event.ocsf.authorizations = [{decision: "Denied", policy: {name: authz_profile}}]
} else {
$event.ocsf.disposition_id = 1
$event.ocsf.disposition = "Allowed"
$event.ocsf.authorizations = [{decision: "Allowed", policy: {name: authz_profile}}]
}
if $event.ise.attributes.AuthorizationPolicyMatchedRule? != null {
$event.ocsf.policy.name = move $event.ise.attributes.AuthorizationPolicyMatchedRule
}
if $event.ise.attributes.ISEPolicySetName? != null {
$event.ocsf.policy.group = {name: move $event.ise.attributes.ISEPolicySetName}
}
}
drop authz_profile
119 changes: 119 additions & 0 deletions cisco/operators/ise/ocsf/events/radius_accounting.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
description: Cisco ISE RADIUS Accounting β†’ OCSF Network Activity
args:
named:
- name: event
description: The field that holds the event to map.
type: field
---

@name = "ocsf.network_activity"
$event.ocsf.category_uid = 4
$event.ocsf.class_uid = 4001

// RADIUS accounting reports session usage, not an authentication decision.
match $event.ise.attributes["Acct-Status-Type"]? {
"Start" => {
$event.ocsf.activity_id = 1
}
"Stop" => {
$event.ocsf.activity_id = 2
}
"Interim-Update" => {
$event.ocsf.activity_id = 6
}
_ => {
$event.ocsf.activity_id = 0
}
}
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

if $event.ise.timestamp? != null {
$event.ocsf.time = move $event.ise.timestamp
}
drop $event.ise.timestamp?
$event.ocsf.message = move $event.ise.message_text?
drop $event.ise.attributes["Event-Timestamp"]?
$event.ocsf.status_id = 1
$event.ocsf.status = "Success"

if ($event.ise.attributes.UserName? else $event.ise.attributes["User-Name"]?) != null {
$event.ocsf.actor.user.name = move $event.ise.attributes.UserName? else move $event.ise.attributes["User-Name"]?
}
drop $event.ise.attributes.UserName?, $event.ise.attributes["User-Name"]?
// ISE `UserType` is `User` (a person) or `Host` (a device identity); `Host` has
// no OCSF user type, so it maps to Other with the raw value.
user_type = move $event.ise.attributes.UserType?
if user_type == "User" {
$event.ocsf.actor.user.type_id = 1
$event.ocsf.actor.user.type = "User"
} else if user_type != null {
$event.ocsf.actor.user.type_id = 99
$event.ocsf.actor.user.type = user_type
}
drop user_type

src_ip = move $event.ise.attributes["Framed-IP-Address"]? else move $event.ise.attributes.IpAddress?
if src_ip != null {
$event.ocsf.src_endpoint.ip = ip(src_ip)
}
drop src_ip, $event.ise.attributes["Framed-IP-Address"]?, $event.ise.attributes.IpAddress?
src_mac = move $event.ise.attributes["Calling-Station-ID"]? else move $event.ise.attributes.EndPointMACAddress?
if src_mac != null {
$event.ocsf.src_endpoint.mac = src_mac
}
drop src_mac, $event.ise.attributes["Calling-Station-ID"]?, $event.ise.attributes.EndPointMACAddress?

device_ip = move $event.ise.attributes["Device IP Address"]? else move $event.ise.attributes["NAS-IP-Address"]?
if device_ip != null {
$event.ocsf.device.ip = ip(device_ip)
}
drop device_ip, $event.ise.attributes["Device IP Address"]?, $event.ise.attributes["NAS-IP-Address"]?
if $event.ise.attributes.NetworkDeviceName? != null {
$event.ocsf.device.hostname = move $event.ise.attributes.NetworkDeviceName
}

// ISE exposes identity groups across user and endpoint fields.
identity_groups = [move $event.ise.attributes.IdentityGroup?, move $event.ise.attributes.HostIdentityGroup?, move $event.ise.attributes.Name?].where(g => g != null).distinct()
if identity_groups.length() > 0 {
$event.ocsf.actor.user.groups = identity_groups.map(name => {name: name})
}
drop identity_groups
if $event.ise.attributes["Model Name"]? != null {
$event.ocsf.device.model = move $event.ise.attributes["Model Name"]
}

// Network Activity has no top-level session, so session ids live under
// `connection_info`.
if $event.ise.attributes["Acct-Session-Id"]? != null {
$event.ocsf.connection_info.uid = move $event.ise.attributes["Acct-Session-Id"]
}
if $event.ise.attributes.AcsSessionID? != null {
$event.ocsf.connection_info.session.uid = move $event.ise.attributes.AcsSessionID
}
if $event.ise.attributes.CPMSessionID? != null {
$event.ocsf.connection_info.session.uid_alt = move $event.ise.attributes.CPMSessionID
}

// RADIUS counters use the NAS perspective: Input is outbound from the user and
// Output is inbound to the user. Gigawords carry the high 32 bits.
$event.ocsf.traffic = {}
if $event.ise.attributes["Acct-Input-Octets"]? != null {
$event.ocsf.traffic.bytes_out = int(move $event.ise.attributes["Acct-Input-Octets"]) + int(move $event.ise.attributes["Acct-Input-Gigawords"]? else "0") * 4294967296
}
if $event.ise.attributes["Acct-Output-Octets"]? != null {
$event.ocsf.traffic.bytes_in = int(move $event.ise.attributes["Acct-Output-Octets"]) + int(move $event.ise.attributes["Acct-Output-Gigawords"]? else "0") * 4294967296
}
if $event.ise.attributes["Acct-Input-Packets"]? != null {
$event.ocsf.traffic.packets_out = int(move $event.ise.attributes["Acct-Input-Packets"])
}
if $event.ise.attributes["Acct-Output-Packets"]? != null {
$event.ocsf.traffic.packets_in = int(move $event.ise.attributes["Acct-Output-Packets"])
}

if $event.ise.attributes["Acct-Session-Time"]? != null {
$event.ocsf.duration = int(move $event.ise.attributes["Acct-Session-Time"]) * 1000
}

drop $event.ise.attributes["Acct-Status-Type"]?
drop $event.ise.attributes["Acct-Input-Gigawords"]?, $event.ise.attributes["Acct-Output-Gigawords"]?
Loading
Loading