Skip to content

Add Cisco Secure Firewall ASA support - #162

Open
zedoraps wants to merge 20 commits into
mainfrom
feat/cisco-asa-package
Open

Add Cisco Secure Firewall ASA support#162
zedoraps wants to merge 20 commits into
mainfrom
feat/cisco-asa-package

Conversation

@zedoraps

Copy link
Copy Markdown
Contributor

🔍 Problem

The cisco package only covered Umbrella DNS. Cisco Secure Firewall ASA
syslog had no parser or OCSF mapping in the library, despite being one of the
most common firewall log sources.

🛠️ Solution

  • cisco::asa::parse — extracts the %ASA-<sev>-<id>: <text> frame from a
    configurable message field (default content). Tolerates any syslog
    header, both legacy (Mon DD [YYYY] HH:MM:SS) and RFC 5424
    (2018-06-27T12:17:46Z) timestamps, and works on read_syslog, read_lines,
    or a log shipper's body field. Grok-based rather than parse_syslog, which
    returns null on common ASA lines (no hostname + non-standard timestamp).
  • cisco::asa::ocsf::map — dispatches by message ID (via match) to:
    • Network Activity (4001): connection built/teardown (+ICMP), ACL /
      protocol / ICMP denies, access-list hit-count (permit & deny),
      duplicate-SYN, to-the-box denies
    • Authentication (3002): VPN session logon/logoff, identity-mapping delete
    • Base Event fallback with original text preserved; message ID recorded in
      metadata.event_code
  • Endpoint addresses classified as ip vs hostname; NAT/translated addresses
    mapped to proxy_endpoint.
  • Examples: syslog-TCP collection + OCSF mapping (same receiving/mapping
    split as umbrella and zscaler).
  • Tests: parse (read_lines and read_syslog paths) and end-to-end OCSF
    mapping, with anonymized fixtures.

Validated against ~2,500 real ASA messages: 99% mapped to a specific OCSF
class
; the remainder (a VPN diagnostic and a few admin messages) fall back to
Base Event by design.

💬 Review

  • Namespace and parameter conventions match the rest of the library: dispatch
    under cisco::asa::ocsf::events::* (mirrors umbrella), event field arg with
    default: this on the mapper.
  • All sample data uses RFC 5737 / RFC 1918 ranges — no real telemetry.
  • metadata.event_code (not original_event_uid) carries the message ID, per
    the OCSF definition that names "Cisco syslog code" as the example.

zedoraps and others added 3 commits June 22, 2026 14:56
Add ASA as a second product in the cisco package, parsing Cisco Secure
Firewall ASA syslog messages and mapping them to OCSF.

cisco::asa::parse extracts the %ASA-<severity>-<id> frame from a
configurable message field (default "content", as produced by
read_syslog), tolerating both legacy and RFC 5424 timestamps and
resolving the event time from the line or the syslog envelope.

cisco::asa::ocsf::map dispatches by message ID to Network Activity
(connections, denies, ACL hits, flagged traffic) and Authentication
(VPN session and identity events), recording the message ID in
metadata.event_code. Unsupported messages fall back to the OCSF Base
Event with their original text preserved.

Add parse and end-to-end OCSF mapping tests with anonymized fixtures,
plus syslog-collection and OCSF-mapping examples.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
read_syslog already strips the syslog envelope, so the parser no longer
re-parses a priority, timestamp, or hostname from the message. It anchors
on the %ASA frame and resolves the event time from the sibling timestamp
that read_syslog leaves behind, covering both the legacy and RFC 5424
formats.

The message handed to parse is expected to start at %ASA, which holds for
read_syslog content, a reconstructed shipper message, and a file of bare
%ASA lines. Tests now read their inputs with read_syslog.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replace the silent drop with an assert so a message that does not carry a
%ASA-<severity>-<id> frame is reported, helping users catch a wrong
`message` field or non-ASA logs routed to the parser. A cheap
starts_with pre-check gates the grok call so valid input stays
warning-free and the generic grok match-failure warning no longer fires
on unrelated input.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@zedoraps
zedoraps force-pushed the feat/cisco-asa-package branch from 30c6a17 to 6409873 Compare June 22, 2026 13:45
zedoraps and others added 3 commits June 22, 2026 15:52
Change the `message` parameter from a string field name to a `field`
argument, matching the parse operators in the paloalto and amazon
packages. The body is read as `$message` instead of `this[$message]`,
and callers pass a field reference (e.g. `message=content`) rather than
a quoted field name.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Each match arm now assigns the parsed body to a `body` record and the fields merge into the event once after the match, instead of repeating the {...this, ...} spread in every arm. This mirrors the one-assignment-per-arm shape of the paloalto and amazon parse operators. Behavior is unchanged.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Declare the security_control profile only in the event mappers that set
firewall fields (built, teardown, deny) rather than globally, matching how
the fortinet package adds it. Map the syslog hostname to the reporting
logger (metadata.loggers[].device.hostname), as fortinet's syslog handling
does, rather than the event's subject device, so the mapping no longer
declares a profile it does not populate.

In the authentication mapper, match the known message IDs explicitly for
both activity_id (722051 logon, 113019/746013 logoff) and status_id
(722051/113019 success), falling back to Unknown so a message unexpectedly
routed here is not mislabeled.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@zedoraps
zedoraps force-pushed the feat/cisco-asa-package branch from 01f4ed9 to 3ea40e0 Compare June 22, 2026 14:07
zedoraps and others added 10 commits June 22, 2026 16:17
Resolve the event time in the OCSF mapper rather than the parser. The syslog timestamp is an envelope field that read_syslog provides, like the hostname, so the mapper parses it into ocsf.time (legacy and RFC 5424 forms) and the parser leaves it untouched.

Merge each parsed body record inline, the same way the frame is merged, instead of via a temp that has to be dropped. This matches the one-assignment-per-match-arm shape of the paloalto parse operator and keeps the frame and body parsing consistent.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
read_syslog already provides the timestamp as a string, so coercing it again before time()/parse_time() is unnecessary. Also remove a stray comment in the built mapper that described logic living in network_endpoints.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Move the connection_id -> connection_info.uid mapping out of the built and teardown mappers into network_endpoints, which both call and which already owns connection_info, removing the duplication.

Collapse the two mirrored protocol branches into one: set a real protocol name, then take the IANA number from the message if it gave one (the `protocol N` form) or look it up by name.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The entry listed only three message families; spell out the full Network Activity coverage and the new Authentication mapping so readers do not underestimate the scope.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The mapper also handles 106100 access-list logs, which record permits as well as denies, so `deny` misnamed the allowed case. `access_control` covers both firewall verdicts.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
No other package emits profiles: []; zscaler and sophos omit the field when there are no profiles. Drop it from the base metadata and have the firewall-decision mappers set profiles: ["security_control"] directly, so only events that populate a profile carry the field.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
It is already recorded in metadata.event_code, so keeping it in unmapped duplicated the value.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replace the single globbed map test with one test per OCSF mapper (built, teardown, access_control, network, authentication, base), each reading its own input file, matching the sophos and suricata packages. A failure now isolates the affected mapper, and the baselines are smaller and easier to review.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
read_syslog always emits app_name and process_id, which ASA leaves empty, and the ASA subsystem context is usually absent. Run drop_null_fields on the source record so unmapped carries only values that are present instead of read_syslog's null placeholders.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Cover two cases the read_syslog parse tests miss: a bare %ASA frame with no syslog envelope (parsed via read_lines + message=line) and a line that is not an ASA frame (rejected by the parser's assertion and dropped).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@zedoraps
zedoraps marked this pull request as ready for review June 22, 2026 14:51
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@zedoraps
zedoraps requested a review from mavam June 22, 2026 14:52
zedoraps and others added 3 commits June 22, 2026 16:54
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Infer years for undated ASA syslog timestamps, clean null endpoint fields, map IPv6 ICMP correctly, and avoid treating numeric-protocol denies as a direction.

The updated ASA tests cover the timestamp regression and refresh the affected parser and OCSF fixtures.

Assisted-by: GPT-5 (Codex)
Parse additional ASA access-control and connection message shapes, including endpoint annotations, Land Attack denies, user-qualified access-list logs, and invalid ICMP denies.

Add anonymized PRI-prefixed examples that document how these variants map to OCSF, while keeping SFR and IKE messages as Base Event for now.

Assisted-by: GPT-5 (Codex)
@zedoraps
zedoraps force-pushed the feat/cisco-asa-package branch from ed1e28a to df9c98e Compare June 22, 2026 16:49
Update the Cisco ASA TCP syslog example to use the current listening operator name.

Assisted-by: GPT-5 (Codex)
Comment on lines +16 to +17
let $ipv4 = r"^\d{1,3}(\.\d{1,3}){3}$"
let $ipv6 = r"^[0-9A-Fa-f:]*:[0-9A-Fa-f:]+$"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also try parsing as ip first, then fallback to hostname. This IP regexing is a not ergonomic since have strong types for it.

// Parse the body of the matched message ID into structured fields. Each body
// pattern is written against `text` alone, and merges into the event the same
// way the frame does above.
match message_id {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this really assign to this? IIRC UDOs should stick within $event, no?

cisco::asa::ocsf::map
ocsf::derive
ocsf::cast
drop metadata.processed_time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably unnecessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants