Add Cisco Secure Firewall ASA support - #162
Open
zedoraps wants to merge 20 commits into
Open
Conversation
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
force-pushed
the
feat/cisco-asa-package
branch
from
June 22, 2026 13:45
30c6a17 to
6409873
Compare
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
force-pushed
the
feat/cisco-asa-package
branch
from
June 22, 2026 14:07
01f4ed9 to
3ea40e0
Compare
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
marked this pull request as ready for review
June 22, 2026 14:51
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
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
force-pushed
the
feat/cisco-asa-package
branch
from
June 22, 2026 16:49
ed1e28a to
df9c98e
Compare
Update the Cisco ASA TCP syslog example to use the current listening operator name. Assisted-by: GPT-5 (Codex)
mavam
approved these changes
Jul 3, 2026
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:]+$" |
Member
There was a problem hiding this comment.
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 { |
Member
There was a problem hiding this comment.
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔍 Problem
The
ciscopackage only covered Umbrella DNS. Cisco Secure Firewall ASAsyslog 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 aconfigurable
messagefield (defaultcontent). Tolerates any syslogheader, both legacy (
Mon DD [YYYY] HH:MM:SS) and RFC 5424(
2018-06-27T12:17:46Z) timestamps, and works onread_syslog,read_lines,or a log shipper's body field. Grok-based rather than
parse_syslog, whichreturns
nullon common ASA lines (no hostname + non-standard timestamp).cisco::asa::ocsf::map— dispatches by message ID (viamatch) to:protocol / ICMP denies, access-list hit-count (permit & deny),
duplicate-SYN, to-the-box denies
metadata.event_codeipvshostname; NAT/translated addressesmapped to
proxy_endpoint.split as umbrella and zscaler).
read_linesandread_syslogpaths) and end-to-end OCSFmapping, 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
under
cisco::asa::ocsf::events::*(mirrors umbrella),eventfield arg withdefault: thison the mapper.metadata.event_code(notoriginal_event_uid) carries the message ID, perthe OCSF definition that names "Cisco syslog code" as the example.