Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
43 changes: 43 additions & 0 deletions cisco/changelog/unreleased/add-cisco-asa-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Add Cisco Secure Firewall ASA support
type: feature
authors:
- zedoraps
prs:
- 162
created: 2026-06-18T00:00:00Z
---

The `cisco` package now parses Cisco Secure Firewall ASA syslog messages and
maps them to OCSF.

`cisco::asa::parse` extracts the `%ASA-<severity>-<message_id>: <text>` frame
from a field and parses the body of supported messages into structured fields.
It takes a `message` field argument (default `content`, as produced by the
built-in `read_syslog`), so it composes with any transport:

```tql
from_tcp "0.0.0.0:514" {
read_syslog
}
cisco::asa::parse
cisco::asa::ocsf::map
ocsf::derive
ocsf::cast
```

Point `message` at another field for other delivery methods, e.g. `message=line`
after `read_lines`, or the body field a log shipper provides.

`cisco::asa::ocsf::map` maps the common firewall messages by ID:

- **OCSF Network Activity (4001)**: connection setup and teardown
(302013/302015, 302014/302016/302021); access-list, protocol, and ICMP denies
plus access-list hit-count logs (106001, 106006/106007, 106010, 106014,
106023, 106100, 313004/313008, 710003/710005); and duplicate TCP SYN (419002).
- **OCSF Authentication (3002)**: VPN session logon and logoff and
identity-mapping changes (722051, 113019, 746013).

Every other message maps to the OCSF Base Event with its original text
preserved. The ASA message ID is recorded in `metadata.event_code`, and the
reporting host in `metadata.loggers`.
13 changes: 13 additions & 0 deletions cisco/examples/asa-from-syslog-tcp.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: Onboard Cisco ASA logs via Syslog TCP
description: |
Receives Cisco Secure Firewall ASA logs over TCP syslog with the built-in
`read_syslog`, parses the ASA payload into normalized events, and publishes
them to the `cisco` topic.
---

accept_tcp "0.0.0.0:514" {
read_syslog
}
cisco::asa::parse
publish "cisco"
13 changes: 13 additions & 0 deletions cisco/examples/asa-map-to-ocsf.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: Cisco ASA β†’ OCSF
description: |
Maps parsed Cisco ASA records from the `cisco` topic to OCSF events and
publishes them to the `ocsf` topic.
---

subscribe "cisco"
where @name == "cisco.asa"
cisco::asa::ocsf::map
ocsf::derive
ocsf::cast
publish "ocsf"
14 changes: 14 additions & 0 deletions cisco/operators/asa/ocsf/base.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description: Cisco ASA unsupported messages β†’ 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
65 changes: 65 additions & 0 deletions cisco/operators/asa/ocsf/events/access_control.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
description: "Cisco ASA access-control decisions β€” denies and access-list permit/deny logs (106001, 106006/7, 106010, 106014, 106023, 106100, 313004/8, 710003/5) β†’ OCSF Network Activity (4001)"
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
$event.ocsf.activity_id = 6 // Traffic
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

cisco::asa::ocsf::network_endpoints event=$event

// The access-group name that produced the decision is the matched firewall rule.
if $event.asa.acl_id? != null {
$event.ocsf.firewall_rule = {
name: move $event.asa.acl_id,
}
}

if $event.asa.user? != null {
$event.ocsf.actor.user.name = move $event.asa.user
}

if $event.asa.reason? != null {
$event.ocsf.status_detail = move $event.asa.reason
}

// Connection direction relative to the firewall, when the message states it.
if $event.asa.direction? != null {
let $directions = {
inbound: 1,
outbound: 2,
}
if $directions[$event.asa.direction]? != null {
$event.ocsf.connection_info.direction_id = $directions[$event.asa.direction]
}
}
drop $event.asa.direction?

// `hit_count` (106100) is the number of times the rule matched in the interval.
if $event.asa.hit_count? != null {
$event.ocsf.count = move $event.asa.hit_count
}

// Access-list decisions populate the security_control profile.
$event.ocsf.metadata.profiles = ["security_control"]
if $event.ocsf.actor? != null {
$event.ocsf.metadata.profiles = $event.ocsf.metadata.profiles.add("host")
}

// 106100 logs both permits and denies via `action`; everything else is a deny.
if $event.asa.action? == "permitted" {
$event.ocsf.disposition_id = 1 // Allowed
$event.ocsf.action_id = 1 // Allowed
} else {
$event.ocsf.disposition_id = 2 // Blocked
$event.ocsf.action_id = 2 // Denied
}
drop $event.asa.action?
66 changes: 66 additions & 0 deletions cisco/operators/asa/ocsf/events/authentication.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
description: "Cisco ASA VPN session / identity messages (722051 logon, 113019 & 746013 logoff) β†’ OCSF Authentication (3002)"
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
// 722051 establishes a session; 113019 and 746013 end one.
let $activities = {
"722051": 1, // Logon
"113019": 2, // Logoff
"746013": 2, // Logoff
}
$event.ocsf.activity_id = $activities[$event.asa.message_id.string()]? else 0
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

if $event.asa.vpn_user? != null {
$event.ocsf.user = {
name: move $event.asa.vpn_user,
}
}
if $event.asa.domain? != null {
$event.ocsf.user.domain = move $event.asa.domain
}
if $event.asa.vpn_group? != null {
$event.ocsf.user.groups = [{name: move $event.asa.vpn_group}]
}

// The client's public address is the source of the session.
if $event.asa.src_ip? != null {
$event.ocsf.src_endpoint = {
ip: move $event.asa.src_ip,
}
}

if $event.asa.reason? != null {
$event.ocsf.status_detail = move $event.asa.reason
}

// 746013 reports an explicit result, either "Succeeded" or "Failed", which maps
// to Success/Failure (an unexpected value maps to Other (99) with the source
// string preserved in `status`, per OCSF conventions). The session logon
// (722051) and logoff (113019) messages carry no status but always describe a
// completed action, so they map to Success. Anything else stays Unknown.
if $event.asa.status? != null {
let $statuses = {
Succeeded: 1, // Success
Failed: 2, // Failure
}
$event.ocsf.status_id = $statuses[$event.asa.status]? else 99
if $event.ocsf.status_id == 99 {
$event.ocsf.status = move $event.asa.status
} else {
drop $event.asa.status?
}
} else if $event.asa.message_id == 722051 or $event.asa.message_id == 113019 {
$event.ocsf.status_id = 1 // Success
} else {
$event.ocsf.status_id = 0 // Unknown
}
30 changes: 30 additions & 0 deletions cisco/operators/asa/ocsf/events/built.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
description: "Cisco ASA connection-built messages (302013/302015) β†’ OCSF Network Activity (4001)"
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
$event.ocsf.activity_id = 1 // Open
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

cisco::asa::ocsf::network_endpoints event=$event

// `direction` is relative to the firewall: an inbound connection is initiated
// from the outside, an outbound one from the inside.
let $directions = {
inbound: 1,
outbound: 2,
}
$event.ocsf.connection_info.direction_id = $directions[$event.asa.direction?]? else 0
drop $event.asa.direction?

// Connection decisions populate the security_control profile.
$event.ocsf.metadata.profiles = ["security_control"]
$event.ocsf.disposition_id = 1 // Allowed
17 changes: 17 additions & 0 deletions cisco/operators/asa/ocsf/events/network.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
description: "Cisco ASA flagged traffic messages (419002 Duplicate TCP SYN) β†’ OCSF Network Activity (4001)"
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
$event.ocsf.activity_id = 6 // Traffic
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

cisco::asa::ocsf::network_endpoints event=$event
32 changes: 32 additions & 0 deletions cisco/operators/asa/ocsf/events/teardown.tql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
description: "Cisco ASA connection-teardown messages (302014/302016) β†’ OCSF Network Activity (4001)"
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
$event.ocsf.activity_id = 2 // Close
$event.ocsf.type_uid = $event.ocsf.class_uid * 100 + $event.ocsf.activity_id

cisco::asa::ocsf::network_endpoints event=$event

// Teardown messages summarize the closed session: total bytes and the reason
// the firewall tore the connection down.
if $event.asa.bytes? != null {
$event.ocsf.traffic = {
bytes: move $event.asa.bytes,
}
}
if $event.asa.reason? != null {
$event.ocsf.status_detail = move $event.asa.reason
}

// Connection decisions populate the security_control profile.
$event.ocsf.metadata.profiles = ["security_control"]
$event.ocsf.disposition_id = 1 // Allowed
Loading
Loading