From d8a6b298ef4f98bb236e73a4847d1fde57af2719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Biset?= <43619595+jbiset@users.noreply.github.com> Date: Thu, 25 Apr 2024 09:14:28 -0300 Subject: [PATCH] [Remove discover] Implement embeddable dashboard on Virustotal module (#6525) * Migrated visualizations, added loadings and messages * Fixed warning for expected a single ReactElement * Added new virustotal data source. Changed NoResults and LoadingSpinner to commons components * Fixed Events tab * Integrated pinned agent functionality based on data source * Added timeRange to dashboard useEffect dependencies and changed the way to get pinned agent * Deleted unused component * Changed AlertsVirustotalDataSource import in modules-defaults * Fixed error message * DashboardByRenderer timeRange params replaced by searchBarProps deconstruction * Added wz-discover hide-filter-control classes to hide the button that allows you to affect all the filters in the search bar * Removed unnecessary virus total in Filters tab in common data to remove duplicate filters * Removed unused getImplicitPinnedAgent in modules-helper * Added dateRange param to fetchData in dashboard useEffect * Improved AlertsVirustotalDataSource import in modules-defaults and deleted wz-discover on SearchBar wrapper --- plugins/main/common/constants.ts | 2 + .../alerts-virustotal-data-source.ts | 24 + .../pattern/alerts/alerts-virustotal/index.ts | 1 + .../data-source/pattern/alerts/index.ts | 3 +- .../common/modules/modules-defaults.tsx | 19 +- .../common/modules/modules-helper.js | 2 + .../virustotal/dashboard/dashboard.tsx | 163 +++ .../virustotal/dashboard/dashboard_panels.ts | 989 ++++++++++++++++++ .../dashboard/dashboard_panels_kpis.ts | 304 ++++++ .../dashboard/virustotal_dashboard.scss | 10 + plugins/main/public/services/common-data.js | 1 - 11 files changed, 1512 insertions(+), 6 deletions(-) create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/alerts-virustotal-data-source.ts create mode 100644 plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/index.ts create mode 100644 plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx create mode 100644 plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels.ts create mode 100644 plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels_kpis.ts create mode 100644 plugins/main/public/components/overview/virustotal/dashboard/virustotal_dashboard.scss diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index e25266536e..6c7a0f3d5c 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -227,6 +227,8 @@ export const DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT = 'pinned-agent'; export const DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER = 'cluster-manager'; export const DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP = 'vulnerabilities-rule-group'; +export const DATA_SOURCE_FILTER_CONTROLLED_VIRUSTOTAL_RULE_GROUP = + 'virustotal-rule-group'; // Wazuh links export const WAZUH_LINK_GITHUB = 'https://github.com/wazuh'; diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/alerts-virustotal-data-source.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/alerts-virustotal-data-source.ts new file mode 100644 index 0000000000..c4d2a3ac61 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/alerts-virustotal-data-source.ts @@ -0,0 +1,24 @@ +import { tFilter } from '../../../index'; +import { DATA_SOURCE_FILTER_CONTROLLED_VIRUSTOTAL_RULE_GROUP } from '../../../../../../../common/constants'; +import { AlertsDataSource } from '../alerts-data-source'; + +const VIRUSTOTAL_GROUP_KEY = 'rule.groups'; +const VIRUSTOTAL_GROUP_VALUE = 'virustotal'; + +export class AlertsVirustotalDataSource extends AlertsDataSource { + constructor(id: string, title: string) { + super(id, title); + } + + getRuleGroupsFilter() { + return super.getRuleGroupsFilter( + VIRUSTOTAL_GROUP_KEY, + VIRUSTOTAL_GROUP_VALUE, + DATA_SOURCE_FILTER_CONTROLLED_VIRUSTOTAL_RULE_GROUP, + ); + } + + getFixedFilters(): tFilter[] { + return [...this.getRuleGroupsFilter(), ...super.getFixedFilters()]; + } +} diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/index.ts b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/index.ts new file mode 100644 index 0000000000..4ec93e3d67 --- /dev/null +++ b/plugins/main/public/components/common/data-source/pattern/alerts/alerts-virustotal/index.ts @@ -0,0 +1 @@ +export * from './alerts-virustotal-data-source'; diff --git a/plugins/main/public/components/common/data-source/pattern/alerts/index.ts b/plugins/main/public/components/common/data-source/pattern/alerts/index.ts index 58def960bf..bff12f4aea 100644 --- a/plugins/main/public/components/common/data-source/pattern/alerts/index.ts +++ b/plugins/main/public/components/common/data-source/pattern/alerts/index.ts @@ -1,3 +1,4 @@ export * from './alerts-vulnerabilities'; export * from './alerts-data-source-repository'; -export * from './alerts-data-source'; \ No newline at end of file +export * from './alerts-data-source'; +export * from './alerts-virustotal'; diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 009e6a2c15..295d52f8cf 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -27,6 +27,7 @@ import { } from '../wazuh-discover/wz-discover'; import { threatHuntingColumns } from '../wazuh-discover/config/data-grid-columns'; import { vulnerabilitiesColumns } from '../../overview/vulnerabilities/events/vulnerabilities-columns'; +import { DashboardVirustotal } from '../../overview/virustotal/dashboard/dashboard'; import React from 'react'; import { dockerColumns } from '../../overview/docker/events/docker-columns'; import { googleCloudColumns } from '../../overview/google-cloud/events/google-cloud-columns'; @@ -44,7 +45,10 @@ import { mitreAttackColumns } from '../../overview/mitre/events/mitre-attack-col import { virustotalColumns } from '../../overview/virustotal/events/virustotal-columns'; import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns'; import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; -import { AlertsVulnerabilitiesDataSource } from '../data-source'; +import { + AlertsVulnerabilitiesDataSource, + AlertsVirustotalDataSource, +} from '../data-source'; const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; @@ -242,10 +246,17 @@ export const ModulesDefaults = { availableFor: ['manager', 'agent'], }, virustotal: { - init: 'dashboard', tabs: [ - DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, virustotalColumns), + { + id: 'dashboard', + name: 'Dashboard', + buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], + component: DashboardVirustotal, + }, + renderDiscoverTab({ + tableColumns: virustotalColumns, + DataSource: AlertsVirustotalDataSource, + }), ], availableFor: ['manager', 'agent'], }, diff --git a/plugins/main/public/components/common/modules/modules-helper.js b/plugins/main/public/components/common/modules/modules-helper.js index 0443f4a40b..2cae56749a 100644 --- a/plugins/main/public/components/common/modules/modules-helper.js +++ b/plugins/main/public/components/common/modules/modules-helper.js @@ -2,6 +2,8 @@ import { getAngularModule, getDataPlugin } from '../../../kibana-services'; import { AppState } from '../../../react-services/app-state'; import { FilterHandler } from '../../../utils/filter-handler'; import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../common/constants'; +import { useFilterManager } from '../hooks'; +import { FilterStateStore } from '../../../../../../src/plugins/data/common'; export class ModulesHelper { static async getDiscoverScope() { diff --git a/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx b/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx new file mode 100644 index 0000000000..811d289a9e --- /dev/null +++ b/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx @@ -0,0 +1,163 @@ +import React, { useState, useEffect } from 'react'; +import { getPlugins } from '../../../../kibana-services'; +import { ViewMode } from '../../../../../../../src/plugins/embeddable/public'; +import { SearchResponse } from '../../../../../../../src/core/server'; +import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { getDashboardPanels } from './dashboard_panels'; +import { I18nProvider } from '@osd/i18n/react'; +import useSearchBar from '../../../common/search-bar/use-search-bar'; +import { getKPIsPanel } from './dashboard_panels_kpis'; +import { + ErrorFactory, + ErrorHandler, + HttpError, +} from '../../../../react-services/error-management'; +import { withErrorBoundary } from '../../../common/hocs/error-boundary/with-error-boundary'; +import { SampleDataWarning } from '../../../visualize/components/sample-data-warning'; +import { + AlertsDataSourceRepository, + PatternDataSource, + tParsedIndexPattern, + useDataSource, +} from '../../../common/data-source'; +import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { DiscoverNoResults } from '../../../common/no-results/no-results'; +import { AlertsVirustotalDataSource } from '../../../common/data-source/pattern/alerts/alerts-virustotal/alerts-virustotal-data-source'; +import './virustotal_dashboard.scss'; + +const plugins = getPlugins(); + +const SearchBar = getPlugins().data.ui.SearchBar; + +const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; +const DashboardVT: React.FC = () => { + const { + filters, + dataSource, + fetchFilters, + isLoading: isDataSourceLoading, + fetchData, + setFilters, + } = useDataSource({ + DataSource: AlertsVirustotalDataSource, + repository: new AlertsDataSourceRepository(), + }); + + const [results, setResults] = useState({} as SearchResponse); + + const { searchBarProps } = useSearchBar({ + indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters, + }); + const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + + useEffect(() => { + if (isDataSourceLoading) { + return; + } + fetchData({ + query, + dateRange: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }) + .then(results => { + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching alerts', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + isDataSourceLoading, + JSON.stringify(fetchFilters), + JSON.stringify(query), + dateRangeFrom, + dateRangeTo, + ]); + + return ( + + <> + {isDataSourceLoading && !dataSource ? ( + + ) : ( +
+ +
+ )} + {!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? ( + + ) : null} + {dataSource && results?.hits?.total === 0 ? ( + + ) : null} + {!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? ( +
+ + 0, + ), + isFullScreenMode: false, + filters: fetchFilters ?? [], + useMargins: true, + id: 'virustotal-dashboard-tab', + timeRange: { + from: dateRangeFrom, + to: dateRangeTo, + }, + title: 'Virustotal dashboard', + description: 'Dashboard of the Virustotal', + query: query, + refreshConfig: { + pause: false, + value: 15, + }, + hidePanelTitles: false, + }} + /> +
+ ) : null} + +
+ ); +}; + +export const DashboardVirustotal = withErrorBoundary(DashboardVT); diff --git a/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels.ts b/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels.ts new file mode 100644 index 0000000000..1ec019da2b --- /dev/null +++ b/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels.ts @@ -0,0 +1,989 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +/* WARNING: The panel id must be unique including general and agents visualizations. Otherwise, the visualizations will not refresh when we pin an agent, because they are cached by id */ + +/* Overview visualizations */ + +const getVisStateTop5UniqueMaliciousFilesPerAgent = ( + indexPatternId: string, +) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Malicious-Per-Agent', + title: 'Top 5 agents with unique malicious files', + type: 'pie', + params: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: 'right', + isDonut: true, + labels: { + show: false, + values: true, + last_level: true, + truncate: 100, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [ + { + meta: { + index: 'wazuh-alerts', + negate: true, + disabled: false, + alias: null, + type: 'phrase', + key: 'data.virustotal.malicious', + value: '0', + params: { + query: '0', + type: 'phrase', + }, + }, + query: { + match: { + 'data.virustotal.malicious': { + query: '0', + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState', + }, + }, + ], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'cardinality', + schema: 'metric', + params: { field: 'data.virustotal.source.md5' }, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'agent.name', + size: 5, + order: 'desc', + orderBy: '1', + }, + }, + ], + }, + }; +}; + +const getVisStateLastScannedFiles = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Last-Files-Pie', + title: 'Last scanned files', + type: 'pie', + params: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: 'right', + isDonut: true, + labels: { + show: false, + values: true, + last_level: true, + truncate: 100, + }, + }, + uiState: { + vis: { legendOpen: true }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Files' }, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'data.virustotal.source.file', + size: 5, + order: 'desc', + orderBy: '1', + }, + }, + ], + }, + }; +}; + +const getVisStateAlertsEvolutionByAgents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Alerts-Evolution', + title: 'Alerts evolution by agents', + type: 'histogram', + params: { + type: 'histogram', + grid: { categoryLines: false }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { type: 'linear' }, + labels: { show: true, filter: true, truncate: 100 }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { type: 'linear', mode: 'normal' }, + labels: { show: true, rotate: 0, filter: false, truncate: 100 }, + title: { text: 'Count' }, + }, + ], + seriesParams: [ + { + show: true, + type: 'histogram', + mode: 'stacked', + data: { label: 'Count', id: '1' }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: { show: false }, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + dimensions: { + x: { + accessor: 0, + format: { id: 'date', params: { pattern: 'YYYY-MM-DD HH:mm' } }, + params: { + date: true, + interval: 'PT3H', + intervalOpenSearchValue: 3, + intervalOpenSearchUnit: 'h', + format: 'YYYY-MM-DD HH:mm', + bounds: { + min: '2020-04-17T12:11:35.943Z', + max: '2020-04-24T12:11:35.944Z', + }, + }, + label: 'timestamp per 3 hours', + aggType: 'date_histogram', + }, + y: [ + { + accessor: 2, + format: { id: 'number' }, + params: {}, + label: 'Count', + aggType: 'count', + }, + ], + series: [ + { + accessor: 1, + format: { + id: 'string', + params: { + parsedUrl: { + origin: 'http://localhost:5601', + pathname: '/app/kibana', + basePath: '', + }, + }, + }, + params: {}, + label: 'Top 5 unusual terms in agent.name', + aggType: 'significant_terms', + }, + ], + }, + radiusRatio: 50, + }, + uiState: { + vis: { + defaultColors: { + '0 - 7': 'rgb(247,251,255)', + '7 - 13': 'rgb(219,233,246)', + '13 - 20': 'rgb(187,214,235)', + '20 - 26': 'rgb(137,190,220)', + '26 - 33': 'rgb(83,158,205)', + '33 - 39': 'rgb(42,123,186)', + '39 - 45': 'rgb(11,85,159)', + }, + legendOpen: true, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [ + { + meta: { + index: 'wazuh-alerts', + negate: false, + disabled: false, + alias: null, + type: 'exists', + key: 'data.virustotal.positives', + value: 'exists', + }, + exists: { + field: 'data.virustotal.positives', + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'wazuh-alerts', + negate: true, + disabled: false, + alias: null, + type: 'phrase', + key: 'data.virustotal.positives', + value: '0', + params: { + query: 0, + type: 'phrase', + }, + }, + query: { + match: { + 'data.virustotal.positives': { + query: 0, + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState', + }, + }, + ], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: {}, + }, + { + id: '3', + enabled: true, + type: 'terms', + schema: 'group', + params: { + field: 'agent.name', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { + field: 'timestamp', + timeRange: { from: 'now-7d', to: 'now' }, + useNormalizedEsInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + }, + ], + }, + }; +}; + +const getVisStateMaliciousFilesAlertsEvolution = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Malicious-Evolution', + title: 'Malicious files alerts evolution', + type: 'histogram', + params: { + type: 'histogram', + grid: { categoryLines: false, style: { color: '#eee' } }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { type: 'linear' }, + labels: { show: true, filter: true, truncate: 100 }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { type: 'linear', mode: 'normal' }, + labels: { show: true, rotate: 0, filter: false, truncate: 100 }, + title: { text: 'Malicious' }, + }, + ], + seriesParams: [ + { + show: 'true', + type: 'histogram', + mode: 'stacked', + data: { label: 'Malicious', id: '1' }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [ + { + meta: { + index: 'wazuh-alerts', + negate: false, + disabled: false, + alias: null, + type: 'exists', + key: 'data.virustotal.malicious', + value: 'exists', + }, + exists: { + field: 'data.virustotal.malicious', + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'wazuh-alerts', + negate: true, + disabled: false, + alias: null, + type: 'phrase', + key: 'data.virustotal.malicious', + value: '0', + params: { + query: 0, + type: 'phrase', + }, + }, + query: { + match: { + 'data.virustotal.malicious': { + query: 0, + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState', + }, + }, + ], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Malicious' }, + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { + field: 'timestamp', + interval: 'auto', + customInterval: '2h', + min_doc_count: 1, + extended_bounds: {}, + }, + }, + ], + }, + }; +}; + +const getVisStateLastFiles = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Files-Table', + title: 'Last files', + type: 'table', + params: { + perPage: 10, + showPartialRows: false, + showMeticsAtAllLevels: false, + sort: { columnIndex: 2, direction: 'desc' }, + showTotal: false, + showToolbar: true, + totalFunc: 'sum', + }, + uiState: { + vis: { params: { sort: { columnIndex: 2, direction: 'desc' } } }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Count' }, + }, + { + id: '4', + enabled: true, + type: 'terms', + schema: 'bucket', + params: { + field: 'data.virustotal.source.file', + size: 10, + order: 'desc', + orderBy: '1', + customLabel: 'File', + }, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'bucket', + params: { + field: 'data.virustotal.permalink', + size: 1, + order: 'desc', + orderBy: '1', + customLabel: 'Link', + }, + }, + ], + }, + }; +}; + +/* Agent visualizations */ + +const getVisStateAgentLastScannedFiles = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-Virustotal-Last-Files-Pie', + title: 'Last scanned files', + type: 'pie', + params: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: 'right', + isDonut: true, + labels: { show: false, values: true, last_level: true, truncate: 100 }, + }, + uiState: { vis: { legendOpen: true } }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Files' }, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'data.virustotal.source.file', + size: 5, + order: 'desc', + orderBy: '1', + }, + }, + ], + }, + }; +}; + +const getVisStateAgentMaliciousFilesAlertsEvolution = ( + indexPatternId: string, +) => { + return { + id: 'Wazuh-App-Agents-Virustotal-Malicious-Evolution', + title: 'Malicious files alerts Evolution', + type: 'histogram', + params: { + type: 'histogram', + grid: { categoryLines: false, style: { color: '#eee' } }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { type: 'linear' }, + labels: { show: true, filter: true, truncate: 100 }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { type: 'linear', mode: 'normal' }, + labels: { show: true, rotate: 0, filter: false, truncate: 100 }, + title: { text: 'Malicious' }, + }, + ], + seriesParams: [ + { + show: 'true', + type: 'histogram', + mode: 'stacked', + data: { label: 'Malicious', id: '1' }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [ + { + meta: { + index: 'wazuh-alerts', + negate: false, + disabled: false, + alias: null, + type: 'exists', + key: 'data.virustotal.positives', + value: 'exists', + }, + exists: { + field: 'data.virustotal.positives', + }, + $state: { + store: 'appState', + }, + }, + { + meta: { + index: 'wazuh-alerts', + negate: true, + disabled: false, + alias: null, + type: 'phrase', + key: 'data.virustotal.positives', + value: '0', + params: { + query: 0, + type: 'phrase', + }, + }, + query: { + match: { + 'data.virustotal.positives': { + query: 0, + type: 'phrase', + }, + }, + }, + $state: { + store: 'appState', + }, + }, + ], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Malicious' }, + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { + field: 'timestamp', + interval: 'auto', + customInterval: '2h', + min_doc_count: 1, + extended_bounds: {}, + }, + }, + ], + }, + }; +}; + +const getVisStateAgentLastFiles = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-Virustotal-Files-Table', + title: 'Last files', + type: 'table', + params: { + perPage: 10, + showPartialRows: false, + showMeticsAtAllLevels: false, + sort: { columnIndex: 2, direction: 'desc' }, + showTotal: false, + showToolbar: true, + totalFunc: 'sum', + }, + uiState: { + vis: { params: { sort: { columnIndex: 2, direction: 'desc' } } }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: 'Count' }, + }, + { + id: '4', + enabled: true, + type: 'terms', + schema: 'bucket', + params: { + field: 'data.virustotal.source.file', + size: 10, + order: 'desc', + orderBy: '1', + customLabel: 'File', + }, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'bucket', + params: { + field: 'data.virustotal.permalink', + size: 1, + order: 'desc', + orderBy: '1', + missingBucket: true, + missingBucketLabel: '-', + customLabel: 'Link', + }, + }, + ], + }, + }; +}; + +/* Definitiion of panels */ + +export const getDashboardPanels = ( + indexPatternId: string, + pinnedAgent?: boolean, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + const pinnedAgentPanels = { + '6': { + gridData: { + w: 12, + h: 9, + x: 0, + y: 0, + i: '6', + }, + type: 'visualization', + explicitInput: { + id: '6', + savedVis: getVisStateAgentLastScannedFiles(indexPatternId), + }, + }, + '7': { + gridData: { + w: 36, + h: 9, + x: 12, + y: 0, + i: '7', + }, + type: 'visualization', + explicitInput: { + id: '7', + savedVis: getVisStateAgentMaliciousFilesAlertsEvolution(indexPatternId), + }, + }, + '8': { + gridData: { + w: 48, + h: 20, + x: 0, + y: 9, + i: '8', + }, + type: 'visualization', + explicitInput: { + id: '8', + savedVis: getVisStateAgentLastFiles(indexPatternId), + }, + }, + }; + + const panels = { + '1': { + gridData: { + w: 24, + h: 13, + x: 0, + y: 0, + i: '1', + }, + type: 'visualization', + explicitInput: { + id: '1', + savedVis: getVisStateTop5UniqueMaliciousFilesPerAgent(indexPatternId), + }, + }, + '2': { + gridData: { + w: 24, + h: 13, + x: 28, + y: 0, + i: '2', + }, + type: 'visualization', + explicitInput: { + id: '2', + savedVis: getVisStateLastScannedFiles(indexPatternId), + }, + }, + '3': { + gridData: { + w: 48, + h: 20, + x: 0, + y: 13, + i: '3', + }, + type: 'visualization', + explicitInput: { + id: '3', + savedVis: getVisStateAlertsEvolutionByAgents(indexPatternId), + }, + }, + '4': { + gridData: { + w: 48, + h: 9, + x: 0, + y: 23, + i: '4', + }, + type: 'visualization', + explicitInput: { + id: '4', + savedVis: getVisStateMaliciousFilesAlertsEvolution(indexPatternId), + }, + }, + '5': { + gridData: { + w: 48, + h: 20, + x: 0, + y: 32, + i: '5', + }, + type: 'visualization', + explicitInput: { + id: '5', + savedVis: getVisStateLastFiles(indexPatternId), + }, + }, + }; + + return pinnedAgent ? pinnedAgentPanels : panels; +}; diff --git a/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels_kpis.ts new file mode 100644 index 0000000000..3a738bcc66 --- /dev/null +++ b/plugins/main/public/components/overview/virustotal/dashboard/dashboard_panels_kpis.ts @@ -0,0 +1,304 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateTotalMalicious = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Total-Malicious', + title: 'Total Malicious', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Reds', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 40, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: ' ' }, + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'data.virustotal.malicious: 1', + language: 'kuery', + }, + label: '- Total malicious', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateTotalPositives = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Total-Positives', + title: 'Total Positives', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Greens', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 40, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: ' ' }, + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'data.virustotal.positives: *', + language: 'kuery', + }, + label: '- Total Positives', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateTotal = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-Virustotal-Total', + title: 'Total', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Greens', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 40, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: { customLabel: ' ' }, + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'data.virustotal:*', + language: 'kuery', + }, + label: '- Total', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +export const getKPIsPanel = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '1': { + gridData: { + w: 12, + h: 6, + x: 6, + y: 0, + i: '1', + }, + type: 'visualization', + explicitInput: { + id: '1', + savedVis: getVisStateTotalMalicious(indexPatternId), + }, + }, + '2': { + gridData: { + w: 12, + h: 6, + x: 18, + y: 0, + i: '2', + }, + type: 'visualization', + explicitInput: { + id: '2', + savedVis: getVisStateTotalPositives(indexPatternId), + }, + }, + '3': { + gridData: { + w: 12, + h: 6, + x: 30, + y: 0, + i: '3', + }, + type: 'visualization', + explicitInput: { + id: '3', + savedVis: getVisStateTotal(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/virustotal/dashboard/virustotal_dashboard.scss b/plugins/main/public/components/overview/virustotal/dashboard/virustotal_dashboard.scss new file mode 100644 index 0000000000..0802d38593 --- /dev/null +++ b/plugins/main/public/components/overview/virustotal/dashboard/virustotal_dashboard.scss @@ -0,0 +1,10 @@ +.virustotal-dashboard-responsive { + @media (max-width: 767px) { + .react-grid-layout { + height: auto !important; + } + .dshLayout-isMaximizedPanel { + height: 100% !important; + } + } +} diff --git a/plugins/main/public/services/common-data.js b/plugins/main/public/services/common-data.js index bd5d18d0f4..4a12c1388c 100644 --- a/plugins/main/public/services/common-data.js +++ b/plugins/main/public/services/common-data.js @@ -144,7 +144,6 @@ export class CommonData { aws: { group: 'amazon' }, gcp: { group: 'gcp' }, office: { group: 'office365' }, - virustotal: { group: 'virustotal' }, osquery: { group: 'osquery' }, sca: { group: 'sca' }, docker: { group: 'docker' },