From a3cc6f025e43bf8c9bf546e2882d7fffdcda8b76 Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Mon, 18 Aug 2025 09:54:40 -0400 Subject: [PATCH] reports --- salt/sensoroni/config.sls | 17 ++ salt/sensoroni/enabled.sls | 1 + .../reports/custom/generic_report1.md | 39 ++++ .../templates/reports/standard/case_report.md | 133 ++++++++++++ .../reports/standard/productivity_report.md | 189 ++++++++++++++++++ salt/sensoroni/soc_sensoroni.yaml | 91 +++++++++ salt/soc/defaults.map.jinja | 1 + salt/soc/defaults.yaml | 2 + salt/soc/enabled.sls | 1 + salt/soc/soc_soc.yaml | 9 + 10 files changed, 483 insertions(+) create mode 100644 salt/sensoroni/files/templates/reports/custom/generic_report1.md create mode 100644 salt/sensoroni/files/templates/reports/standard/case_report.md create mode 100644 salt/sensoroni/files/templates/reports/standard/productivity_report.md diff --git a/salt/sensoroni/config.sls b/salt/sensoroni/config.sls index f983fce38..68b3baddc 100644 --- a/salt/sensoroni/config.sls +++ b/salt/sensoroni/config.sls @@ -43,6 +43,23 @@ analyzerscripts: - source: salt://sensoroni/files/analyzers - show_changes: False +templatesdir: + file.directory: + - name: /opt/so/conf/sensoroni/templates + - user: 939 + - group: 939 + - makedirs: True + +sensoronitemplates: + file.recurse: + - name: /opt/so/conf/sensoroni/templates + - source: salt://sensoroni/files/templates + - user: 939 + - group: 939 + - mode: 664 + - template: jinja + - show_changes: False + sensoroni_sbin: file.recurse: - name: /usr/sbin diff --git a/salt/sensoroni/enabled.sls b/salt/sensoroni/enabled.sls index 3f05568a0..fd44ea79a 100644 --- a/salt/sensoroni/enabled.sls +++ b/salt/sensoroni/enabled.sls @@ -22,6 +22,7 @@ so-sensoroni: - /nsm/pcapout:/nsm/pcapout:rw - /opt/so/conf/sensoroni/sensoroni.json:/opt/sensoroni/sensoroni.json:ro - /opt/so/conf/sensoroni/analyzers:/opt/sensoroni/analyzers:rw + - /opt/so/conf/sensoroni/templates:/opt/sensoroni/templates:r - /opt/so/log/sensoroni:/opt/sensoroni/logs:rw - /nsm/suripcap/:/nsm/suripcap:rw {% if DOCKER.containers['so-sensoroni'].custom_bind_mounts %} diff --git a/salt/sensoroni/files/templates/reports/custom/generic_report1.md b/salt/sensoroni/files/templates/reports/custom/generic_report1.md new file mode 100644 index 000000000..133480731 --- /dev/null +++ b/salt/sensoroni/files/templates/reports/custom/generic_report1.md @@ -0,0 +1,39 @@ +{{- /* query.myDocEvents.Oql = metadata.type: _doc | groupby event.module, event.dataset | sortby @timestamp desc */ -}} +{{- /* query.myDocEvents.MetricLimit = 10 */ -}} +{{- /* query.myDocEvents.EventLimit = 100 */ -}} + +SECURITY ONION SAMPLE REPORT +============================ + +{{ if .Error }} +**NOTE: This report encountered a problem extracting the relevant data and may not be complete.** + +**Error:** {{.Error}} +{{ end }} + + +Records must have been created or updated during the following time frame in order to be reflected in this report. + +**Report Start Date:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .BeginDate}} + +**Report End Date:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .EndDate}} + +## Sample Doc Events + +**Total Events:** {{ formatNumber "%d" "en" .Results.myDocEvents.TotalEvents}} + +### Event Counts By Module and Dataset + +| Count | Proportion | Module | Dataset | +| ----- | ---------- | ------ | ------- | +{{ range sortMetrics "Value" "desc" .Results.myDocEvents.Metrics.groupby_0_event_module_event_dataset -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | {{index .Keys 1}} | +{{end}} + +### Individual Events (Limited to first {{.Results.myDocEvents.Criteria.EventLimit}}) + +| Event Time | Module | Dataset | Category | +| ---------- | ------ | ------- | -------- | +{{ range .Results.myDocEvents.Events -}} +| {{.Timestamp}} | {{.Payload.event_module}} | {{.Payload.event_dataset}} | {{.Payload.event_category}} | +{{end}} diff --git a/salt/sensoroni/files/templates/reports/standard/case_report.md b/salt/sensoroni/files/templates/reports/standard/case_report.md new file mode 100644 index 000000000..ab563040a --- /dev/null +++ b/salt/sensoroni/files/templates/reports/standard/case_report.md @@ -0,0 +1,133 @@ +SECURITY ONION CASE REPORT +========================== + +## Case Details + +**Case ID:** {{.Case.Id}} + +**Title:** {{.Case.Title}} + +## Description + +{{.Case.Description}} + +## Details + +**Created:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .Case.CreateTime}} + +**Updated:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .Case.UpdateTime}} + +**Author:** {{getUserDetail "email" .Case.UserId}} + +**Status:** {{.Case.Status}} + +**TLP:** {{.Case.Tlp}} + +**PAP:** {{.Case.Pap}} + +**Severity:** {{.Case.Severity}} + +**Priority:** {{.Case.Priority}} + +**Category:** {{.Case.Category}} + +**Tags:** {{join .Case.Tags ", " }} + +**Assignee:** {{getUserDetail "email" .Case.AssigneeId}} + +**Hours Logged:** {{ formatNumber "%.2f" "en" .TotalHours}} + +## Comments + +{{ range sortComments "CreateTime" "asc" .Comments }} +**Created:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .CreateTime}} + +**Updated:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .UpdateTime}} + +**Author:** {{getUserDetail "email" .UserId}} + +**Hours Logged:** {{ formatNumber "%.2f" "en" .Hours}} + +{{.Description}} + +--- + +{{end}} + +## Detections + +{{ range sortDetections "Title" "asc" .Detections }} +**Title:** {{.Title}} + +**Description:** {{.Description}} + +**Severity:** {{.Severity}} + +**Rule Engine:** {{.Engine}} + +**Rule Set:** {{.Ruleset}} + +**Community Rule:** {{.IsCommunity}} + +**Tags:** {{.Tags}} + +{{.Content}} + +--- + +{{end}} + +## Attachments + +{{ range sortArtifacts "CreateTime" "asc" .Attachments }} +**Added:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .CreateTime}} + +**Updated:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .UpdateTime}} + +**Added By:** {{getUserDetail "email" .UserId}} + +**TLP:** {{.Tlp}} + +**Filename:** {{.Value}} + +**Size:** {{ formatNumber "%.0d" "en" .StreamLen}} bytes + +**SHA256:** {{.Sha256}} + +**SHA1:** {{.Sha1}} + +**MD5:** {{.Md5}} + +**Tags:** {{.Tags}} + +**Protected (Zipped):** {{.Protected}} + +{{.Description}} + +--- + +{{end}} + +## Observables + +| Date Added | Tlp | Type | IOC | Value | Description | +| ---------- | --- | ---- | --- | ----- | ----------- | +{{ range sortArtifacts "CreateTime" "asc" .Observables -}} +| {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .CreateTime}} | {{.Tlp}} | {{.ArtifactType}} | {{.Ioc}} | {{.Value}} | {{.Description}} | +{{end}} + +## Related Events + +| Event Time | Log ID | Source IP | Destination IP | +| ---------- | ------ | --------- | -------------- | +{{ range sortRelatedEvents "fields:soc_timestamp" "asc" .RelatedEvents -}} +| {{.Fields.soc_timestamp}} | {{.Fields.log_id_uid}} | {{.Fields.source_ip}} | {{.Fields.destination_ip}} | +{{end}} + +## Case History + +| Date | User | Object | Operation | +| ---- | ---- | ------ | --------- | +{{ range sortHistory "CreateTime" "asc" .History -}} +| {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .CreateTime}} | {{getUserDetail "email" .UserId}} | {{.Kind}} | {{.Operation}} | +{{end}} \ No newline at end of file diff --git a/salt/sensoroni/files/templates/reports/standard/productivity_report.md b/salt/sensoroni/files/templates/reports/standard/productivity_report.md new file mode 100644 index 000000000..2e99e710f --- /dev/null +++ b/salt/sensoroni/files/templates/reports/standard/productivity_report.md @@ -0,0 +1,189 @@ +SECURITY ONION PRODUCTIVITY REPORT +================================== + +{{ if .Error }} +**NOTE: This report encountered a problem extracting the relevant data and may not be complete.** + +**Error:** {{.Error}} +{{ end }} + + +Records must have been created or updated during the following time frame in order to be reflected in this report. + +**Report Start Date:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .BeginDate}} + +**Report End Date:** {{formatDateTime "Mon Jan 02 15:04:05 -0700 2006" .EndDate}} + +## Ingested Events + +**Total Events:** {{ formatNumber "%d" "en" .TotalEvents}} + +### Events By Module + +| Count | Proportion | Module | +| ----- | ---------- | ------ | +{{ range sortMetrics "Value" "desc" .TotalEventsByModule -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Events By Module and Severity Label + +| Count | Proportion | Module | Severity | +| ----- | ---------- | ------ | -------- | +{{ range sortMetrics "Value" "desc" .TotalEventsByModuleDataset -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | {{index .Keys 1}} | +{{end}} + +## Alerts + +**Total Alerts:** {{ formatNumber "%d" "en" .TotalAlerts}} + +{{ range sortMetrics "Value" "desc" .TotalAlertsByAcknowledged -}} + {{ if index .Keys 0 | eq "true" }} +**Acknowledged Alerts:** {{ formatNumber "%.0f" "en" .Value}} ({{ formatNumber "%.1f" "en" .Percentage}}%) + {{ end }} +{{end}} + +{{ range sortMetrics "Value" "desc" .TotalAlertsByEscalated -}} + {{ if index .Keys 0 | eq "true" }} +**Escalated Alerts:** {{ formatNumber "%.0f" "en" .Value}} ({{ formatNumber "%.1f" "en" .Percentage}}%) + {{ end }} +{{end}} + +### Alerts By Severity + +| Count | Proportion | Severity | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalAlertsBySeverityLabel -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Alerts By Module + +| Count | Proportion | Module | +| ----- | ---------- | ------ | +{{ range sortMetrics "Value" "desc" .TotalAlertsByModule -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Alerts By Module and Severity Label + +| Count | Proportion | Module | Severity | +| ----- | ---------- | ------ | -------- | +{{ range sortMetrics "Value" "desc" .TotalAlertsByModuleSeverityLabel -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | {{index .Keys 1}} | +{{end}} + +### Alerts By Ruleset + +| Count | Proportion | Ruleset | +| ----- | ---------- | ------- | +{{ range sortMetrics "Value" "desc" .TotalAlertsByRuleset -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Alerts By Rule Category + +| Count | Proportion | Category | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalAlertsByCategory -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +## Cases + +**Total Cases:** {{ formatNumber "%d" "en" .TotalCases}} + +**Average Elapsed Time To Complete:** {{ formatNumber "%.1f" "en" .AverageHoursToComplete }} hours + +### Cases By Status + +| Count | Proportion | Status | +| ----- | ---------- | ------ | +{{ range sortMetrics "Value" "desc" .TotalCasesByStatus -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Assignee + +| Count | Proportion | Assignee | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalCasesByAssignee -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0 | getUserDetail "email"}} | +{{end}} + +### Cases By Status and Assignee + +| Count | Proportion | Status | Assignee | +| ----- | ---------- | ------ | -------- | +{{ range sortMetrics "Value" "desc" .TotalCasesByStatusAssignee -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | {{index .Keys 1 | getUserDetail "email"}} | +{{end}} + +### Cases By Severity + +| Count | Proportion | Severity | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalCasesBySeverity -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Priority + +| Count | Proportion | Priority | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalCasesByPriority -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Traffic Light Protocol (TLP) + +| Count | Proportion | TLP | +| ----- | ---------- | ----| +{{ range sortMetrics "Value" "desc" .TotalCasesByTlp -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Permissible Actions Protocol (PAP) + +| Count | Proportion | PAP | +| ----- | ---------- | --- | +{{ range sortMetrics "Value" "desc" .TotalCasesByPap -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Category + +| Count | Proportion | Category | +| ----- | ---------- | -------- | +{{ range sortMetrics "Value" "desc" .TotalCasesByCategory -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Cases By Tags + +| Count | Proportion | Tags | +| ----- | ---------- | ---- | +{{ range sortMetrics "Value" "desc" .TotalCasesByTags -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0}} | +{{end}} + +### Comments By User + +| Count | Proportion | User | +| ----- | ---------- | ---- | +{{ range sortMetrics "Value" "desc" .TotalCommentsByUserId -}} +| {{ formatNumber "%.0f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0 | getUserDetail "email"}} | +{{end}} + +## Time Tracking + +**Total Hours:** {{ formatNumber "%.2f" "en" .TotalHours}} + +### Hours By User + +| Hours | Proportion | User | +| ----- | ---------- | ---- | +{{ range sortMetrics "Value" "desc" .TotalHoursByUserId -}} +| {{ formatNumber "%.2f" "en" .Value}} | {{ formatNumber "%.1f" "en" .Percentage}}% | {{index .Keys 0 | getUserDetail "email"}} | +{{end}} \ No newline at end of file diff --git a/salt/sensoroni/soc_sensoroni.yaml b/salt/sensoroni/soc_sensoroni.yaml index 71a2c779b..b9ed7bec7 100644 --- a/salt/sensoroni/soc_sensoroni.yaml +++ b/salt/sensoroni/soc_sensoroni.yaml @@ -306,3 +306,94 @@ sensoroni: sensitive: False advanced: True forcedType: string + files: + templates: + reports: + standard: + case_report__md: + title: Case report Template + description: The template used when generating a case report. Supports markdown format. + file: True + global: True + syntax: md + helpLink: reports.html + productivity_report__md: + title: Productivity Report Template + description: The template used when generating a comprehensive productivity report. Supports markdown format. + file: True + global: True + syntax: md + helpLink: reports.html + custom: + generic_report1__md: + title: Custom Report 1 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report2__md: + title: Custom Report 2 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report3__md: + title: Custom Report 3 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report4__md: + title: Custom Report 4 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report5__md: + title: Custom Report 5 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report6__md: + title: Custom Report 6 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report7__md: + title: Custom Report 7 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report8__md: + title: Custom Report 8 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + generic_report9__md: + title: Custom Report 9 + description: A customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. + file: True + global: True + syntax: md + helpLink: reports.html + addl_generic_report__md: + title: Additional Custom Report + description: A duplicatable customer defined report. Supports markdown format. The report title inside the file, typically near the top, will be shown in the SOC reporting UI. This is an unsupported feature due to the inability to edit duplicated reports via the SOC app. + advanced: True + file: True + global: True + syntax: md + duplicates: True + helpLink: reports.html \ No newline at end of file diff --git a/salt/soc/defaults.map.jinja b/salt/soc/defaults.map.jinja index b52aa97d8..a2e72bcca 100644 --- a/salt/soc/defaults.map.jinja +++ b/salt/soc/defaults.map.jinja @@ -35,5 +35,6 @@ {% do SOCDEFAULTS.soc.config.server.modules.statickeyauth.update({'anonymousCidr': DOCKER.range, 'apiKey': pillar.sensoroni.config.sensoronikey}) %} {% do SOCDEFAULTS.soc.config.server.client.case.update({'analyzerNodeId': GLOBALS.hostname}) %} +{% do SOCDEFAULTS.soc.config.server.client.update({'exportNodeId': GLOBALS.hostname}) %} {% set SOCDEFAULTS = SOCDEFAULTS.soc %} diff --git a/salt/soc/defaults.yaml b/salt/soc/defaults.yaml index 66355fa24..95b569afd 100644 --- a/salt/soc/defaults.yaml +++ b/salt/soc/defaults.yaml @@ -1343,6 +1343,7 @@ soc: htmlDir: html importUploadDir: /nsm/soc/uploads forceUserOtp: false + customReportsPath: /opt/sensoroni/templates/reports/custom modules: cases: soc filedatastore: @@ -1557,6 +1558,7 @@ soc: casesEnabled: true detectionsEnabled: true inactiveTools: ['toolUnused'] + exportNodeId: tools: - name: toolKibana description: toolKibanaHelp diff --git a/salt/soc/enabled.sls b/salt/soc/enabled.sls index 09e2c16a8..df6e36dc1 100644 --- a/salt/soc/enabled.sls +++ b/salt/soc/enabled.sls @@ -48,6 +48,7 @@ so-soc: - /opt/so/conf/soc/custom_roles:/opt/sensoroni/rbac/custom_roles:ro - /opt/so/conf/soc/soc_users_roles:/opt/sensoroni/rbac/users_roles:rw - /opt/so/conf/soc/soc_clients_roles:/opt/sensoroni/rbac/clients_roles:rw + - /opt/so/conf/sensoroni/templates:/opt/sensoroni/templates:r - /opt/so/conf/soc/queue:/opt/sensoroni/queue:rw - /opt/so/saltstack:/opt/so/saltstack:rw - /opt/so/conf/soc/migrations:/opt/so/conf/soc/migrations:rw diff --git a/salt/soc/soc_soc.yaml b/salt/soc/soc_soc.yaml index da3549039..8f3b26846 100644 --- a/salt/soc/soc_soc.yaml +++ b/salt/soc/soc_soc.yaml @@ -138,6 +138,11 @@ soc: title: Require TOTP description: Require all users to enable Time-based One Time Passwords (MFA) upon login to SOC. global: True + customReportsPath: + title: Custom Reports Path + description: Path to custom markdown templates for PDF report generation. All markdown files in this directory will be available as custom reports in the SOC Reports interface. + global: True + advanced: True subgrids: title: Subordinate Grids description: | @@ -589,6 +594,10 @@ soc: global: True advanced: True forcedType: "[]{}" + exportNodeId: + description: The node ID on which export jobs will be executed. + global: True + advanced: True hunt: &appSettings groupItemsPerPage: description: Default number of aggregations to show per page. Larger values consume more vertical area in the SOC UI.