Skip to content

v12 dashboard: Bitdefender GravityZone #2712

Description

@kryonsx

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

Goal

Ship a built-in Bitdefender GravityZone dashboard that shows, at a glance, how much Bitdefender GravityZone is sending, whether it is still sending, and what kinds of events they are. The heart of it is the list of Bitdefender GravityZone 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) BITDEFENDER
Data type antivirus-bitdefender-gz
How the logs arrive UTMStack registers itself in GravityZone's Event Push Service (service type 'cef'), and GravityZone then posts batches of CEF events over HTTPS to the plugin on port 8000; each event whose BitdefenderGZCompanyId matches a configured company ID becomes one log.
Parser definitions/filters/antivirus/bitdefender_gz.yaml
What dataSource holds The name of the UTMStack config group (one GravityZone connection with its API key and company IDs). Proof: plugins/bitdefender/server/message.go CreateMessage sets DataSource: cnf.GroupName for the group whose company ID appears in the event; the parser never writes dataSource. So W6 is 'Logs by GravityZone connection'; the endpoint is in target.host.
Grouped by log.eventType (event types)

Why log.eventType: The CEF header grok writes the CEF 'Name' slot to log.eventType on every event. GravityZone fills it with a readable, fixed English name per event module ('AntiMalware', 'Behavioral scanning', 'Firewall', 'Web Control'), so it is low cardinality and needs no translation. The same information exists as a short code in log.BitdefenderGZModule (av, avc, fw, uc), which the KPI filters use because the codes are listed in the plugin's push subscription.

Typical values: AntiMalware (module av), Behavioral scanning (module avc, Advanced Threat Control), Exploit Mitigation (module antiexploit), Ransomware Detection (module ransomware-mitigation), Web Control (module uc), Firewall (module fw), New Incident (module new-incident)

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 GravityZone events in the selected time range.
W2 Threat detections number logs: count; filter log.BitdefenderGZModule is one of [av, avc, antiexploit, ransomware-mitigation, network-sandboxing, exchange-malware, network-monitor] Events from the threat modules: antimalware, Advanced Threat Control, anti-exploit, ransomware mitigation, Sandbox Analyzer, Exchange malware and Network Attack Defense.
W3 Threats not removed number logs: count; filter actionResult = failed GravityZone left the threat in place: action 'still present', 'ignored', 'no action' or 'reportOnly', which the parser marks actionResult 'failed'.
W4 Alerts number alerts: count Alerts raised from GravityZone logs.
W5 Log volume over time area chart logs: count over time Shows gaps in the push feed and bursts of events.
W6 Logs by GravityZone connection bar chart logs: top 10 values of dataSource dataSource is the UTMStack config group of the GravityZone connection, so this shows which connection is busiest or silent.
W7 Top event types value and count table logs: top 25 values of log.eventType; filter log.eventType exists The main list: GravityZone event types by count; click one to open those logs.
W8 Event types over time (top 5) line chart logs: count over time, one line per value of log.eventType (top 5); filter log.eventType exists When each of the five most common event types happened.
W9 Top malware bar chart logs: top 10 values of target.malware; filter target.malware exists Most frequent malware names.
W10 Top endpoints bar chart logs: top 10 values of target.host; filter target.host exists Endpoints with the most events.
W11 Top users bar chart logs: top 10 values of target.user; filter target.user exists Users logged on when detections happened.
W12a Actions taken bar chart logs: top 10 values of action; filter action exists What GravityZone did (blocked, deleted, quarantined, still present, site blocked and so on).
W12b Top file paths bar chart logs: top 10 values of target.path; filter target.path exists Files and programs detected most often (antimalware, behavior, anti-exploit events).
W13 Alerts by rule bar chart alerts: top 10 values of name Which GravityZone detection rules fire most.
W14 Alerts by severity bar chart alerts: top 50 values of severity Split of GravityZone alerts into low, medium and high.
W15 Latest logs table of latest logs logs: latest 20 records; columns @timestamp, dataSource, log.eventType, target.host, target.malware, action, target.user, target.path The newest events with endpoint, malware, action, user and path.

Fields used and where they come from

  • log.eventType: Event name from the CEF header. Examples: AntiMalware, Behavioral scanning, Antiphishing, Task Status. Source: CEF header grok on log.restData (fieldName log.eventType) + trim suffix '|'.
  • log.BitdefenderGZModule: GravityZone event module code. Examples: av, avc, antiexploit, ransomware-mitigation, network-sandboxing. Source: kv step on log.restData (key BitdefenderGZModule, not renamed); codes match plugins/bitdefender/config/template.go subscribeToEventTypes.
  • target.malware: Full malware name. Examples: EICAR-Test-File (not a virus), Trojan.GenericKD.1234. Source: rescue grok for BitdefenderGZMalwareName= (keeps spaces) -> log.BitdefenderGZMalwareNameFull -> rename to target.malware.
  • target.host: Endpoint name (CEF dvchost). Examples: Desktop-JDO, Graylog-Win11. Source: kv (dvchost) -> rename log.dvchost -> target.host.
  • target.user: User on the endpoint (CEF suser). Examples: jdoe, stefan@graylog.com. Source: rescue grok for suser= -> log.suserFull -> rename to target.user.
  • action: What GravityZone did (CEF act, or BitdefenderGZMainAction for incidents). Examples: blocked, deleted, quarantined, still present, uc_site_blocked. Source: rescue grok for act= -> log.actFull; rename [log.actFull, log.BitdefenderGZMainAction] -> action.
  • actionResult: 'success' when the threat was stopped, 'failed' when it was left in place. Examples: success, failed. Source: add 'success' where action in [blocked, block, aph_blocked, portscan_blocked, deleted, disinfected, quarantined, restored]; add 'failed' where action in ['still present', ignored, 'no action', reportOnly].
  • target.path: Path of the detected file or process. Examples: C:\\Users\\jdoe\\Downloads\\b93ef2d1-160c-4bd9-9cbb-cb59ca59939e.tmp. Source: rescue grok for filePath= -> log.filePathFull -> rename to target.path.
  • severity: CEF header severity number, as text. Examples: 3, 9, 10. Source: CEF header grok (log.severity) + trim, then rename log.severity -> severity.

Watch out for

  • The plugin forwards an event only when it contains 'BitdefenderGZCompanyId=' for one of the configured company IDs (plugins/bitdefender/server/message.go); anything else is dropped before parsing and never counted.
  • HyperDetect ('hd') events are not in the plugin's push subscription and Product Modules Status ('modules') is set to false (plugins/bitdefender/config/template.go), so those events never arrive even though the parser handles them. Install, registration and uninstall events are off too.
  • The standard severity column holds the CEF severity number as text ('3', '9', '10'), not low, medium or high, so there is no severity widget.
  • actionResult only covers some actions: 'success' for blocked, block, aph_blocked, portscan_blocked, deleted, disinfected, quarantined and restored; 'failed' for 'still present', ignored, 'no action' and reportOnly. Other values such as 'Blocked' (behavioral scanning), 'uc_site_blocked', 'data_protection_blocked' and 'kill' get none; W12a shows the raw action instead.
  • W2 counts events of the threat modules av, avc, antiexploit, ransomware-mitigation, network-sandboxing, exchange-malware and network-monitor (module codes from the plugin's subscription; names from Bitdefender's event types page).
  • Values with spaces are rescued only for act, filePath, sproc, fname, suser, request, BitdefenderGZDetectionName, BitdefenderGZMalwareName and BitdefenderGZAttackTypes. Other keys are cut at the first space; for example dvchost 'Computer 1' becomes 'Computer' in target.host.
  • CEF escapes are not decoded, so backslashes in paths stay doubled (for example 'C:\Users\jdoe\...').
  • dataSource is the config group, so W6 shows one bar per GravityZone connection, not per endpoint.
  • deviceTime is set from the detection time (BitdefenderGZDetectionTime, start or end), but the time charts use @timestamp, which is when the plugin received the push.
  • No field used here depends on the engine's underscore rule (GravityZone CEF keys have no underscores); the replay gave the same result with both rules. Checked with a step-by-step replay of this filter over 20 events, including GravityZone push samples published in Sekoia's documentation.

Parser problems found while designing this

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

  • severity gets the raw CEF number (rename log.severity -> severity) instead of low, medium or high, so any view that compares severity across products mixes numbers and words. Fix: keep the number in log.cefSeverity and map it (for example 0-3 low, 4-6 medium, 7-10 high).
  • actionResult matching is case-sensitive and incomplete: 'Blocked' from behavioral scanning, 'uc_site_blocked', 'data_protection_blocked' and 'kill' get no actionResult.
  • Keys outside the rescue list are cut at the first space (dvchost 'Computer 1' becomes 'Computer').
  • log.BitdefenderGZMalwareName keeps a cut copy from the kv step ('EICAR-Test-File') next to the full target.malware, because it is not in the final delete list.
  • Plugin, not parser: the company IDs are split on ',' without trimming (config/module.go), but the setup guide says 'comma-separated'; an ID typed after ', ' never matches and all its events are dropped.
  • Plugin, not parser: the push subscription leaves out 'hd' (HyperDetect), so HyperDetect detections never reach UTMStack.

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-bitdefender.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 Bitdefender GravityZone integration, seeded by
# backend/modules/dashboards/repository/dashboard_bootstrap.go.
# Field names come from definitions/filters/antivirus/bitdefender_gz.yaml.
# Verify every widget against real logs before shipping.
name: "Bitdefender GravityZone"
description: "What Bitdefender GravityZone 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: "antivirus-bitdefender-gz"
      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: "antivirus-bitdefender-gz"
      chart: metric
      metric:
        agg: count
      filters:
        - field: "log.BitdefenderGZModule"
          op: in
          value: ["av", "avc", "antiexploit", "ransomware-mitigation", "network-sandboxing", "exchange-malware", "network-monitor"]
    config:
      __builder:
        chartType: metric
        title: "Threat detections"

  - layout: { x: 6, y: 0, w: 3, h: 2 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: metric
      metric:
        agg: count
      filters:
        - field: "actionResult"
          op: eq
          value: "failed"
    config:
      __builder:
        chartType: metric
        title: "Threats not removed"

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

  - layout: { x: 0, y: 2, w: 8, h: 4 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      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: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "dataSource"
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Logs by GravityZone connection"

  - layout: { x: 0, y: 6, w: 6, h: 6 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "log.eventType"
      filters:
        - field: "log.eventType"
          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: "antivirus-bitdefender-gz"
      chart: time
      metric:
        agg: count
      dimension: "log.eventType"
      filters:
        - field: "log.eventType"
          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: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "target.malware"
      filters:
        - field: "target.malware"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top malware"

  - layout: { x: 4, y: 12, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "target.host"
      filters:
        - field: "target.host"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top endpoints"

  - layout: { x: 8, y: 12, w: 4, h: 4 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "target.user"
      filters:
        - field: "target.user"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top users"

  - layout: { x: 0, y: 16, w: 6, h: 4 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "action"
      filters:
        - field: "action"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Actions taken"

  - layout: { x: 6, y: 16, w: 6, h: 4 }
    spec:
      dataset: logs
      dataType: "antivirus-bitdefender-gz"
      chart: category
      metric:
        agg: count
      dimension: "target.path"
      filters:
        - field: "target.path"
          op: exists
      limit: 10
    config:
      __builder:
        chartType: bar
        title: "Top file paths"

  - layout: { x: 0, y: 20, w: 6, h: 4 }
    spec:
      dataset: alerts
      dataType: "antivirus-bitdefender-gz"
      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: "antivirus-bitdefender-gz"
      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: "antivirus-bitdefender-gz"
      chart: table
      metric:
        agg: count
      limit: 20
      columns: ["@timestamp", "dataSource", "log.eventType", "target.host", "target.malware", "action", "target.user", "target.path"]
    config:
      __builder:
        chartType: table
        title: "Latest logs"

Done when

  • definitions/dashboards/integration-bitdefender.yaml is merged to release/v12.0.0 and go test ./modules/dashboards/... passes in backend/.
  • With Bitdefender GravityZone 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