Skip to content

v12 dashboard: SonicWall #2706

Description

@kryonsx

Part of: #2696 · Needs first: #2697 (click-through to the Log Explorer and the value/count table)
Related: #1715 (parsers and rules for this integration, owned by the detection team)

Goal

Ship a built-in SonicWall dashboard that shows, at a glance, how much SonicWall is sending, whether it is still sending, and what kinds of events they are. The heart of it is the list of SonicWall logs grouped by event types (widget W7): click one and the Log Explorer opens on exactly those logs.

Where the data comes from

Integration (catalog name) SONIC_WALL
Data type firewall-sonicwall
How the logs arrive The SonicWall firewall sends syslog over UDP or TCP port 7009 to the UTMStack forwarder, which passes each line unchanged to the server; the UTMStack setup guide tells users to set the SonicWall syslog format to ArcSight (CEF).
Parser definitions/filters/sonicwall/sonic_wall.yaml
What dataSource holds The IP address the syslog message came from, as seen by the forwarder: the firewall itself, or a relay in between. If the sender is 127.0.0.1 it is the forwarder machine's hostname. Proof: collectors/forwarder/collector/syslog/listener.go (UDP) and handler.go (TCP) set DataSource: resolveRemoteAddr(addr); log-input/ingest/server.go applyDefaults only writes 'unknown' when it is empty; the parser never writes dataSource. So W6 is 'Logs by firewall IP'.
Grouped by log.eventName (event types)

Why log.eventName: SonicWall gives every event a fixed name (for example 'Connection Opened') and a numeric message ID. In the CEF format that the UTMStack setup guide asks for (frontend en.json integrations.setup.collector.sonicwall.step2.body), the parser's CEF header grok writes the name to log.eventName and the ID to log.eventCode on every line. The name is readable and of moderate cardinality (a few dozen common names). log.eventCode is the numeric companion (text such as '537') and exists in both syslog formats, so it is shown in the latest-logs table. In the default (non-CEF) SonicWall format log.eventName does not exist and the same text is in log.message instead (see caveats and parser issues). The examples below come from SonicWall's log reference and the repo's SonicWall rules, not from parser code.

Typical values: Connection Opened, Connection Closed, Web site hit, IPS Prevention Alert: <signature name>, Administrator login denied due to bad credentials

Widgets

Standard layout from the parent issue; rows W2, W3 and W9 onward are specific to this integration.

# Title Shown as Query Why
W1 Total logs number logs: count All SonicWall logs in the selected time range.
W2 Denied connections number logs: count; filter actionResult = denied Traffic the firewall dropped or denied; the parser maps fw_action drop/deny/block to actionResult 'denied'.
W3 Critical events number logs: count; filter severity = critical SonicWall priority 0 to 2 (emergency, alert, critical), which the parser maps to severity 'critical'.
W4 Alerts number alerts: count Alerts raised from SonicWall logs.
W5 Log volume over time area chart logs: count over time Shows gaps and spikes in what the firewalls send.
W6 Logs by firewall IP bar chart logs: top 10 values of dataSource dataSource is the sending firewall's IP address, so this shows which firewall is busiest or silent.
W7 Top event types value and count table logs: top 25 values of log.eventName; filter log.eventName exists The main list: SonicWall event names by count; click one to open those logs. The exists filter hides a blank row.
W8 Event types over time (top 5) line chart logs: count over time, one line per value of log.eventName (top 5); filter log.eventName exists When each of the five most common event types happened.
W9 Top source IPs bar chart logs: top 10 values of origin.ip; filter origin.ip exists Hosts that start the most connections.
W10 Top destination ports bar chart logs: top 10 values of target.port; filter target.port exists Which services the traffic goes to.
W11 Top source countries bar chart logs: top 10 values of origin.geolocation.country; filter origin.geolocation.country exists Where connections from public IPs come from (private addresses get no country).
W12a Top firewall rules bar chart logs: top 10 values of log.rule; filter log.rule exists Which access rules match the most traffic.
W12b Top users bar chart logs: top 10 values of origin.user; filter origin.user exists Most active signed-in users (logins, VPN, and traffic identified by single sign-on).
W13 Alerts by rule bar chart alerts: top 10 values of name Which SonicWall detection rules fire most.
W14 Alerts by severity bar chart alerts: top 50 values of severity Split of SonicWall alerts into low, medium and high.
W15 Latest logs table of latest logs logs: latest 20 records; columns @timestamp, dataSource, log.eventName, log.eventCode, origin.ip, target.ip, target.port, action The newest raw records; log.eventCode identifies the event even on lines without log.eventName.

Fields used and where they come from

  • log.eventName: Event name from the CEF header (the 'Name' slot). Examples: Connection Opened, Web site hit. Source: CEF header grok on raw, pattern with fieldName log.eventName, where: contains("raw", "CEF:")
  • log.eventCode: SonicWall message ID, stored as text. Examples: 537, 98. Source: CEF header grok (fieldName log.eventCode); rename log.m -> log.eventCode; cast log.eventCode to string
  • log.message: Event name in the default (non-CEF) format, from msg="...". Examples: Connection Opened. Source: delete log.msg + rescue grok '{{.data}}(msg=)' -> log.message, then the trailing 'key=' strip grok and the quote trims
  • action: Firewall action from fw_action="...". Examples: forward, drop. Source: fw_action rescue grok -> log.actionRaw -> quote trims -> rename log.actionRaw -> action
  • actionResult: 'success' when action is forward; 'denied' when action is drop, dropped, deny, denied, block or blocked. Examples: success, denied. Source: add actionResult 'success' where equals("action", "forward"); add actionResult 'denied' where oneOf("action", [drop, dropped, deny, denied, block, blocked])
  • severity: From the SonicWall priority pri=: 0-2 critical, 3 error, 4 warning, 5-6 info, 7 debug. Examples: info, critical. Source: rename log.pri -> log.priority; cast to string; add severity steps
  • log.groupCategory: SonicWall group category label mapped from gcat=1..17. Examples: Security Services, Network, VPN. Source: rename log.gcat -> log.groupCategoryId; cast to string; add log.groupCategory steps
  • origin.ip: Source IPv4 address. Examples: 192.168.1.10. Source: grok on log.src (ipv4 ':' integer ':' word -> origin.ip, origin.port, log.sourceInterface); rename log.src -> origin.ip when there is no ':'
  • target.port: Destination port (number). Examples: 443, 53. Source: grok on log.dst -> target.ip, target.port, log.targetInterface; cast target.port to int
  • origin.geolocation.country: Country name of a public source IP. Examples: United States. Source: dynamic plugin com.utmstack.geolocation, source origin.ip, destination origin.geolocation (JSON key 'country' per go-sdk plugins.Geolocation)
  • origin.user: User name from usr="...". Examples: jdoe. Source: usr rescue grok -> log.userRaw -> quote trims -> rename log.userRaw -> origin.user
  • log.rule: Firewall rule text from rule="...". Examples: 5 (LAN->WAN). Source: delete log.rule + rule rescue grok -> log.rule, strip grok, quote trims
  • protocol: Transport protocol (the part of proto= before '/'). Examples: tcp, udp. Source: grok on log.proto ('{{.word}}' -> protocol, '/' , '{{.notSpace}}' -> log.appProto); rename log.proto -> protocol when there is no '/'

Watch out for

  • Field names will change: since 24 Sep 2026 the event engine keeps underscores in field names (go-sdk v1.1.35, threatwinds/EventProcessor e6d9bd307e). The v11 parsers were updated for it the same day (utmstack/UTMStack 06e2746), but this v12 parser still renames the old underscore-free names. SonicWall's key is fw_action, so the rename from log.fwaction to action stops matching on the new engine, and every widget on action or actionResult (including W2) goes empty until the v12 SonicWall parser is updated.
  • Syslog format decides the event-name field. The setup guide tells users to choose ArcSight (CEF); then log.eventName and log.eventCode come from the CEF header and exist on every line. A firewall left on the default SonicWall format has no log.eventName (the same text is in log.message), so W7 and W8 are empty for it. log.eventCode exists in both formats.
  • Everything outside the CEF header (addresses, ports, fw_action, pri, gcat, usr, rule) comes from a key=value pass over the whole line. The parser's own header comment says the CEF extension carries the same keys as the default format. This could not be verified with a real SonicWall CEF line. If a firmware uses standard CEF keys instead (spt, dpt, suser, act, and no pri or gcat), then target.port, origin.user, log.rule, action, actionResult and severity are missing and W2, W3, W10, W12a and W12b stay at zero.
  • actionResult is only set to 'success' (fw_action forward) or 'denied' (drop, dropped, deny, denied, block, blocked). Other fw_action values, such as management traffic or 'NA', get no actionResult.
  • origin.ip and target.ip exist only when src/dst is a plain IPv4 address or exactly IPv4:port:interface. IPv6 addresses and other shapes are deleted by the parser (see parser issues).
  • Countries exist only for public source IPs: the geolocation plugin skips private ranges (plugins/geolocation/geolocate.go IsLocal).
  • origin.user exists only on events that carry usr= (logins, VPN, and traffic identified by single sign-on). log.rule exists only on events that carry rule=.
  • Per SonicWall's log reference, some event names include a detail such as the intrusion signature name or a blocked country and IP address, so one kind of event can spread over several rows in W7.
  • Values are case sensitive: severity and actionResult values are lowercase ('critical', 'denied').
  • Parser behavior was read from the filter and from library code, not run: the engine ships as a binary. Two rules used above are stated or implied by the shipped filters: a grok writes nothing unless its whole pattern matches (comment in definitions/filters/fortinet/fortinet.yaml), and the key=value step strips characters other than letters, digits and dots from key names (go-sdk utils.SanitizeField; the Sophos filter's renames such as log.srcip depend on it).

Parser problems found while designing this

These are not dashboard work, but they limit what the dashboard can show. They belong to #1715; raise them there rather than working around them in the dashboard.

  • fw_action is lost when it is the last key on the line: 'delete log.fwaction where contains(raw, fw_action=")' runs first, and the rescue grok then needs another key=value after the quoted value to match. On such lines action and actionResult are missing, so W2 undercounts. Fix: let the rescue pattern accept the end of the line, or delete only after a successful rescue.
  • src/dst addresses can disappear: the grok on log.src (and log.dst) only matches IPv4:port:interface, but the next step deletes log.src whenever it contains ':' even if the grok failed. IPv6 addresses, an empty port (IP::X0) or a missing interface lose the address, so the IP, port and country widgets miss those events. Fix: delete only when origin.ip (target.ip) was set.
  • The event name lives in two fields: log.eventName (CEF header) and log.message (default format). The seven SonicWall rules use log.message for their text checks and never log.eventName, so in the CEF format the setup guide recommends those checks only work if the CEF extension also carries msg="...". Fix: have the CEF header grok also write log.message, or copy msg into log.eventName, so rules and this dashboard work in both formats.
  • The only standard CEF extension keys the parser renames are smac, dmac and cs6. If SonicWall CEF lines use spt, dpt, suser or act, or carry the priority only in the header severity (log.cefSeverity), then ports, user, action and severity stay empty. Could not verify which keys SonicWall sends.
  • Does not affect this dashboard: the renames of log.af_service and log.af_type never match because the key=value step strips '_' from key names (this same filter already relies on that for log.fwaction). log.appFirewallService and log.appFirewallType therefore never exist, yet the botnet and Capture ATP rules test them.
  • Does not affect this dashboard: with the usual '<134>id=firewall' start, the first key becomes '134id' after '<' and '>' are stripped, so rename log.id -> log.sourceId never runs.

How to build it

Before you start: parser field names are changing while the parsers are updated for the engine's new underscore handling (see "Field names are about to move" in #2696). Check every field in this issue against the v12 parser at that moment and against real logs, and build against what you find.

  1. Add definitions/dashboards/integration-sonic-wall.yaml. Keep it in the top folder: the test that checks shipped dashboards (TestEveryShippedDashboardDefinitionIsValid) only reads the top folder.
  2. Start from the file below; it follows the table above and passes the same checks as the backend (domain.Spec.Validate).
  3. W7 is a value/count table: a category query shown with chart type table. It already renders; v12 dashboards: groundwork for integration dashboards (click-through to the Log Explorer, value/count table, Agents dashboard fix) #2697 makes its rows clickable and its headers readable.
  4. Load real logs from this technology on a v12 test server (or replay samples) and check every widget before opening the pull request.
Starting dashboard file
# Dashboard version v1.0.0
#
# System-owned default dashboard for the SonicWall integration, seeded by
# backend/modules/dashboards/repository/dashboard_bootstrap.go.
# Field names come from definitions/filters/sonicwall/sonic_wall.yaml.
# Verify every widget against real logs before shipping.
name: "SonicWall"
description: "What SonicWall is sending: volume, event types, and the activity worth a look."
widgets:
  - layout: { x: 0, y: 0, w: 3, h: 2 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: metric
      metric:
        agg: count
    config:
      __builder:
        chartType: metric
        title: "Total logs"

  - layout: { x: 3, y: 0, w: 3, h: 2 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: metric
      metric:
        agg: count
      filters:
        - field: "actionResult"
          op: eq
          value: "denied"
    config:
      __builder:
        chartType: metric
        title: "Denied connections"

  - layout: { x: 6, y: 0, w: 3, h: 2 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: metric
      metric:
        agg: count
      filters:
        - field: "severity"
          op: eq
          value: "critical"
    config:
      __builder:
        chartType: metric
        title: "Critical events"

  - layout: { x: 9, y: 0, w: 3, h: 2 }
    spec:
      dataset: alerts
      dataType: "firewall-sonicwall"
      chart: metric
      metric:
        agg: count
    config:
      __builder:
        chartType: metric
        title: "Alerts"

  - layout: { x: 0, y: 2, w: 8, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: time
      metric:
        agg: count
    config:
      __builder:
        chartType: area
        title: "Log volume over time"

  - layout: { x: 8, y: 2, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "dataSource"
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Logs by firewall IP"

  - layout: { x: 0, y: 6, w: 6, h: 6 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "log.eventName"
      filters:
        - field: "log.eventName"
          op: exists
      limit: 25
    config:
      __builder:
        chartType: table
        title: "Top event types"

  - layout: { x: 6, y: 6, w: 6, h: 6 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: time
      metric:
        agg: count
      dimension: "log.eventName"
      filters:
        - field: "log.eventName"
          op: exists
      limit: 5
    config:
      __builder:
        chartType: line
        title: "Event types over time (top 5)"

  - layout: { x: 0, y: 12, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "origin.ip"
      filters:
        - field: "origin.ip"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top source IPs"

  - layout: { x: 4, y: 12, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "target.port"
      filters:
        - field: "target.port"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top destination ports"

  - layout: { x: 8, y: 12, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "origin.geolocation.country"
      filters:
        - field: "origin.geolocation.country"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top source countries"

  - layout: { x: 0, y: 16, w: 6, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "log.rule"
      filters:
        - field: "log.rule"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top firewall rules"

  - layout: { x: 6, y: 16, w: 6, h: 4 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "origin.user"
      filters:
        - field: "origin.user"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top users"

  - layout: { x: 0, y: 20, w: 6, h: 4 }
    spec:
      dataset: alerts
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "name"
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Alerts by rule"

  - layout: { x: 6, y: 20, w: 6, h: 4 }
    spec:
      dataset: alerts
      dataType: "firewall-sonicwall"
      chart: category
      metric:
        agg: count
      dimension: "severity"
    config:
      __builder:
        chartType: bar
        title: "Alerts by severity"

  - layout: { x: 0, y: 24, w: 12, h: 6 }
    spec:
      dataset: logs
      dataType: "firewall-sonicwall"
      chart: table
      metric:
        agg: count
      limit: 20
      columns: ["@timestamp", "dataSource", "log.eventName", "log.eventCode", "origin.ip", "target.ip", "target.port", "action"]
    config:
      __builder:
        chartType: table
        title: "Latest logs"

Done when

  • definitions/dashboards/integration-sonic-wall.yaml is merged to release/v12.0.0 and go test ./modules/dashboards/... passes in backend/.
  • With SonicWall logs flowing on a v12 test server, every widget shows data. A widget that stays empty while logs arrive means a wrong field or value: fix it, don't ship it.
  • Clicking a row, bar or line point opens the Log Explorer on the same logs, and the Log Explorer count matches the widget.
  • A time range with no logs shows empty states, not errors.
  • A screenshot of the finished dashboard is attached to this issue.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions