diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 295d52f8cf..cc2d854642 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 { DashboardThreatHunting } from '../../overview/threat-hunting/dashboard/dashboard'; import { DashboardVirustotal } from '../../overview/virustotal/dashboard/dashboard'; import React from 'react'; import { dockerColumns } from '../../overview/docker/events/docker-columns'; @@ -46,6 +47,7 @@ import { virustotalColumns } from '../../overview/virustotal/events/virustotal-c import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns'; import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; import { + AlertsDataSource, AlertsVulnerabilitiesDataSource, AlertsVirustotalDataSource, } from '../data-source'; @@ -85,8 +87,16 @@ export const ModulesDefaults = { general: { init: 'events', tabs: [ - DashboardTab, - renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns), + { + id: 'dashboard', + name: 'Dashboard', + buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], + component: DashboardThreatHunting, + }, + renderDiscoverTab({ + tableColumns: threatHuntingColumns, + DataSource: AlertsDataSource, + }), ], availableFor: ['manager', 'agent'], }, diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx new file mode 100644 index 0000000000..e812c2f832 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx @@ -0,0 +1,329 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { getPlugins, getWazuhCorePlugin } 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 { + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiDataGrid, + EuiToolTip, + EuiDataGridCellValueElementProps, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, +} from '@elastic/eui'; +import { + ErrorFactory, + ErrorHandler, + HttpError, +} from '../../../../react-services/error-management'; +import { + MAX_ENTRIES_PER_QUERY, + exportSearchToCSV, +} from '../../../common/data-grid/data-grid-service'; +import { useDocViewer } from '../../../common/doc-viewer/use-doc-viewer'; +import { useDataGrid } from '../../../common/data-grid/use-data-grid'; +import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; +import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers/format_number_with_commas'; +import DocViewer from '../../../common/doc-viewer/doc-viewer'; +import { withErrorBoundary } from '../../../common/hocs/error-boundary/with-error-boundary'; +import './threat_hunting_dashboard.scss'; +import { SampleDataWarning } from '../../../visualize/components/sample-data-warning'; +import { + threatHuntingTableAgentColumns, + threatHuntingTableDefaultColumns, +} from '../events/threat-hunting-columns'; +import { + AlertsDataSource, + AlertsDataSourceRepository, + PatternDataSource, + PatternDataSourceFilterManager, + tParsedIndexPattern, + useDataSource, +} from '../../../common/data-source'; +import { DiscoverNoResults } from '../../../common/no-results/no-results'; +import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; + +const plugins = getPlugins(); + +const SearchBar = getPlugins().data.ui.SearchBar; + +const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; + +const DashboardTH: React.FC = () => { + const { + filters, + dataSource, + fetchFilters, + isLoading: isDataSourceLoading, + fetchData, + setFilters, + } = useDataSource({ + DataSource: AlertsDataSource, + repository: new AlertsDataSourceRepository(), + }); + + const [results, setResults] = useState({} as SearchResponse); + + const { searchBarProps } = useSearchBar({ + indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters, + }); + const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + + const [inspectedHit, setInspectedHit] = useState(undefined); + const [isExporting, setIsExporting] = useState(false); + + const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); + + const onClickInspectDoc = useMemo( + () => (index: number) => { + const rowClicked = results.hits.hits[index]; + setInspectedHit(rowClicked); + }, + [results], + ); + + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { + const inspectHintMsg = 'Inspect document details'; + return ( + + onClickInspectDoc(rowIndex)} + iconType='inspect' + aria-label={inspectHintMsg} + /> + + ); + }; + + const dataGridProps = useDataGrid({ + ariaLabelledBy: 'Threat Hunting Table', + defaultColumns: threatHuntingTableDefaultColumns, + results, + indexPattern: dataSource?.indexPattern, + DocViewInspectButton, + }); + + const { pagination, sorting, columnVisibility } = dataGridProps; + + const docViewerProps = useDocViewer({ + doc: inspectedHit, + indexPattern: dataSource?.indexPattern, + }); + + const pinnedAgent = + PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!) + .length > 0; + + useEffect(() => { + const currentColumns = !pinnedAgent + ? threatHuntingTableDefaultColumns + : threatHuntingTableAgentColumns; + columnVisibility.setVisibleColumns(currentColumns.map(({ id }) => id)); + }, [pinnedAgent]); + + useEffect(() => { + if (isDataSourceLoading) { + return; + } + fetchData({ + query, + pagination, + sorting, + dateRange: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }) + .then(results => { + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching threat hunting', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query), + JSON.stringify(pagination), + JSON.stringify(sorting), + dateRangeFrom, + dateRangeTo, + ]); + + const onClickExportResults = async () => { + const params = { + indexPattern: dataSource?.indexPattern, + filters: fetchFilters ?? [], + query, + fields: columnVisibility.visibleColumns, + pagination: { + pageIndex: 0, + pageSize: results.hits.total, + }, + sorting, + }; + try { + setIsExporting(true); + await exportSearchToCSV(params); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error downloading csv report', + }); + ErrorHandler.handleError(searchError); + } finally { + setIsExporting(false); + } + }; + + return ( + + <> + {isDataSourceLoading && !dataSource ? ( + + ) : ( +
+ +
+ )} + {!isDataSourceLoading && dataSource && results?.hits?.total === 0 ? ( + + ) : null} + {!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? ( + <> + +
+ + + + {}} + tooltip={ + results?.hits?.total && + results?.hits?.total > MAX_ENTRIES_PER_QUERY + ? { + ariaLabel: 'Warning', + content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas( + MAX_ENTRIES_PER_QUERY, + )} hits.`, + iconType: 'alert', + position: 'top', + } + : undefined + } + /> + + Export Formated + + + ), + }} + /> + {inspectedHit && ( + setInspectedHit(undefined)} size='m'> + + +

Document details

+
+
+ + + + + + + +
+ )} +
+ + ) : null} + +
+ ); +}; + +export const DashboardThreatHunting = withErrorBoundary(DashboardTH); diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels.ts b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels.ts new file mode 100644 index 0000000000..9a2e982178 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels.ts @@ -0,0 +1,1031 @@ +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 getVisStateTop10AlertLevelEvolution = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Alert-level-evolution', + title: 'Top 10 Alert level evolution', + type: 'area', + params: { + type: 'area', + grid: { + categoryLines: true, + style: { + color: '#eee', + }, + valueAxis: 'ValueAxis-1', + }, + 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: 'area', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + interpolate: 'cardinal', + valueAxis: 'ValueAxis-1', + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + labels: {}, + }, + 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', + params: {}, + schema: 'metric', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'rule.level', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + params: { + field: 'timestamp', + timeRange: { + from: 'now-24h', + to: 'now', + }, + useNormalizedOpenSearchInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateTop5Agents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Top-5-agents', + title: 'Top 5 agents', + 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: {}, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'agent.name', + size: 5, + order: 'desc', + orderBy: '1', + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + ], + }, + }; +}; + +const getVisStateTop10MITREATTACKS = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Alerts-Top-Mitre', + title: 'Top 10 MITRE ATT&CKS', + 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: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + schema: 'metric', + params: {}, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'rule.mitre.technique', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + ], + }, + }; +}; + +const getVisStateAlertEvolutionTop5Agents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Alerts-evolution-Top-5-agents', + title: 'Alerts evolution - Top 5 agents', + 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: 'Count' }, + }, + ], + seriesParams: [ + { + show: 'true', + type: 'histogram', + mode: 'stacked', + data: { label: 'Count', id: '1' }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + }, + 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: {}, + }, + { + id: '3', + enabled: true, + type: 'terms', + schema: 'group', + params: { + field: 'agent.name', + size: 5, + order: 'desc', + orderBy: '1', + }, + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { + field: 'timestamp', + interval: 'auto', + customInterval: '2h', + min_doc_count: 1, + extended_bounds: {}, + }, + }, + ], + }, + }; +}; + +/* Agent visualizations */ + +const getVisStatePinnedAgentTop10AlertGroupsEvolution = ( + indexPatternId: string, +) => { + return { + id: 'Wazuh-App-Agents-General-Alert-groups-evolution', + title: 'Top 10 Alert groups evolution', + type: 'area', + params: { + type: 'area', + grid: { + categoryLines: true, + style: { + color: '#eee', + }, + valueAxis: 'ValueAxis-1', + }, + 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: 'area', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + interpolate: 'cardinal', + valueAxis: 'ValueAxis-1', + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + labels: {}, + }, + 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', + params: {}, + schema: 'metric', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'rule.groups', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + params: { + field: 'timestamp', + timeRange: { + from: 'now-1M', + to: 'now', + }, + useNormalizedOpenSearchInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateAlertsAgents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-General-Alerts', + title: 'Alerts', + type: 'area', + params: { + type: 'area', + grid: { + categoryLines: true, + style: { + color: '#eee', + }, + valueAxis: 'ValueAxis-1', + }, + 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: 'area', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + interpolate: 'cardinal', + valueAxis: 'ValueAxis-1', + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + labels: {}, + }, + 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', + params: {}, + schema: 'metric', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'rule.level', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + params: { + field: 'timestamp', + timeRange: { + from: 'now-1M', + to: 'now', + }, + useNormalizedOpenSearchInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateTop5AlertsAgents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-General-Top-5-alerts', + title: 'Top 5 alerts', + 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: {}, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'rule.description', + size: 5, + order: 'desc', + orderBy: '1', + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + ], + }, + }; +}; + +const getVisStateTop5RuleGroupsAgents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-General-Top-10-groups', + title: 'Top 5 rule groups', + 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: {}, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'rule.groups', + size: 5, + order: 'desc', + orderBy: '1', + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + ], + }, + }; +}; + +const getVisStateTop5PCIDSSRequirementsAgents = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Agents-General-Top-5-PCI-DSS-Requirements', + title: 'Top 5 PCI DSS Requirements', + 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: {}, + }, + { + id: '2', + enabled: true, + type: 'terms', + schema: 'segment', + params: { + field: 'rule.pci_dss', + size: 5, + order: 'desc', + orderBy: '1', + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + }, + ], + }, + }; +}; + +/* Definitiion of panels */ + +export const getDashboardPanels = ( + indexPatternId: string, + pinnedAgent?: boolean, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + const pinnedAgentPanels = { + '9': { + gridData: { + w: 24, + h: 13, + x: 0, + y: 0, + i: '9', + }, + type: 'visualization', + explicitInput: { + id: '9', + savedVis: + getVisStatePinnedAgentTop10AlertGroupsEvolution(indexPatternId), + }, + }, + '10': { + gridData: { + w: 24, + h: 13, + x: 24, + y: 0, + i: '10', + }, + type: 'visualization', + explicitInput: { + id: '10', + savedVis: getVisStateAlertsAgents(indexPatternId), + }, + }, + '11': { + gridData: { + w: 16, + h: 13, + x: 0, + y: 13, + i: '11', + }, + type: 'visualization', + explicitInput: { + id: '11', + savedVis: getVisStateTop5AlertsAgents(indexPatternId), + }, + }, + '12': { + gridData: { + w: 16, + h: 13, + x: 16, + y: 13, + i: '12', + }, + type: 'visualization', + explicitInput: { + id: '12', + savedVis: getVisStateTop5RuleGroupsAgents(indexPatternId), + }, + }, + '13': { + gridData: { + w: 16, + h: 13, + x: 32, + y: 13, + i: '13', + }, + type: 'visualization', + explicitInput: { + id: '13', + savedVis: getVisStateTop5PCIDSSRequirementsAgents(indexPatternId), + }, + }, + }; + + const panels = { + '5': { + gridData: { + w: 28, + h: 13, + x: 0, + y: 0, + i: '5', + }, + type: 'visualization', + explicitInput: { + id: '5', + savedVis: getVisStateTop10AlertLevelEvolution(indexPatternId), + }, + }, + '6': { + gridData: { + w: 20, + h: 13, + x: 28, + y: 0, + i: '6', + }, + type: 'visualization', + explicitInput: { + id: '6', + savedVis: getVisStateTop10MITREATTACKS(indexPatternId), + }, + }, + '7': { + gridData: { + w: 15, + h: 12, + x: 0, + y: 13, + i: '7', + }, + type: 'visualization', + explicitInput: { + id: '7', + savedVis: getVisStateTop5Agents(indexPatternId), + }, + }, + '8': { + gridData: { + w: 33, + h: 12, + x: 15, + y: 13, + i: '8', + }, + type: 'visualization', + explicitInput: { + id: '8', + savedVis: getVisStateAlertEvolutionTop5Agents(indexPatternId), + }, + }, + }; + + return pinnedAgent ? pinnedAgentPanels : panels; +}; diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels_kpis.ts new file mode 100644 index 0000000000..772add24a0 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard_panels_kpis.ts @@ -0,0 +1,388 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateTotal = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Metric-alerts', + 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: '- Total -' }, + }, + ], + }, + }; +}; + +const getVisStateLevel12Alerts = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Level-12-alerts', + title: 'Level 12 or above alerts', + 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: 'rule.level >= 12', + language: 'kuery', + }, + label: '- Level 12 or above alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateAuthenticationFailure = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Authentication-failure', + title: 'Authentication failure', + 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: + 'rule.groups: "win_authentication_failed" OR rule.groups: "authentication_failed" OR rule.groups: "authentication_failures"', + language: 'kuery', + }, + label: '- Authentication failure', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateAuthenticationSuccess = (indexPatternId: string) => { + return { + id: 'Wazuh-App-Overview-General-Authentication-success', + title: 'Authentication success', + 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, + }, + }, + }, + uiState: { + vis: { defaultColors: { '0 - 100': 'rgb(0,104,55)' } }, + }, + 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: 'rule.groups: "authentication_success"', + language: 'kuery', + }, + label: '- Authentication success', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +export const getKPIsPanel = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '1': { + gridData: { + w: 12, + h: 6, + x: 0, + y: 0, + i: '1', + }, + type: 'visualization', + explicitInput: { + id: '1', + savedVis: getVisStateTotal(indexPatternId), + }, + }, + '2': { + gridData: { + w: 12, + h: 6, + x: 12, + y: 0, + i: '2', + }, + type: 'visualization', + explicitInput: { + id: '2', + savedVis: getVisStateLevel12Alerts(indexPatternId), + }, + }, + '3': { + gridData: { + w: 12, + h: 6, + x: 24, + y: 0, + i: '3', + }, + type: 'visualization', + explicitInput: { + id: '3', + savedVis: getVisStateAuthenticationFailure(indexPatternId), + }, + }, + '4': { + gridData: { + w: 12, + h: 6, + x: 36, + y: 0, + i: '4', + }, + type: 'visualization', + explicitInput: { + id: '4', + savedVis: getVisStateAuthenticationSuccess(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/index.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/index.tsx new file mode 100644 index 0000000000..b58b6c9229 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/index.tsx @@ -0,0 +1 @@ +export * from './dashboard'; diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/threat_hunting_dashboard.scss b/plugins/main/public/components/overview/threat-hunting/dashboard/threat_hunting_dashboard.scss new file mode 100644 index 0000000000..c8e78aed47 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/threat_hunting_dashboard.scss @@ -0,0 +1,10 @@ +.th-dashboard-responsive { + @media (max-width: 767px) { + .react-grid-layout { + height: auto !important; + } + .dshLayout-isMaximizedPanel { + height: 100% !important; + } + } +} diff --git a/plugins/main/public/components/overview/threat-hunting/events/threat-hunting-columns.tsx b/plugins/main/public/components/overview/threat-hunting/events/threat-hunting-columns.tsx new file mode 100644 index 0000000000..3da4573673 --- /dev/null +++ b/plugins/main/public/components/overview/threat-hunting/events/threat-hunting-columns.tsx @@ -0,0 +1,121 @@ +import { EuiDataGridColumn, EuiLink } from '@elastic/eui'; +import { tDataGridColumn } from '../../../common/data-grid'; +import { getCore } from '../../../../kibana-services'; +import React from 'react'; +import { RedirectAppLinks } from '../../../../../../../src/plugins/opensearch_dashboards_react/public'; + +export const MAX_ENTRIES_PER_QUERY = 10000; + +export const threatHuntingTableDefaultColumns: tDataGridColumn[] = [ + { + id: 'icon', + }, + { + id: 'timestamp', + }, + { + id: 'agent.id', + render: (value: any) => { + const destURL = getCore().application.getUrlForApp('endpoints-summary', { + path: `#/agents?tab=welcome&agent=${value}`, + }); + return ( + + + {value} + + + ); + }, + }, + { + id: 'agent.name', + }, + { + id: 'rule.mitre.id', + render: (value: any) => { + const destURL = getCore().application.getUrlForApp('mitre-attack', { + path: `#/overview/?tab=mitre&tabView=intelligence&tabRedirect=techniques&idToRedirect=${value}`, + }); + return ( + + + {value} + + + ); + }, + }, + { + id: 'rule.mitre.tactic', + }, + { + id: 'rule.description', + }, + { + id: 'rule.level', + }, + { + id: 'rule.id', + render: (value: any) => { + const destURL = getCore().application.getUrlForApp('rules', { + path: `manager/?tab=ruleset&redirectRule=${value}`, + }); + return ( + + + {value} + + + ); + }, + }, +]; + +export const threatHuntingTableAgentColumns: EuiDataGridColumn[] = [ + { + id: 'icon', + }, + { + id: 'timestamp', + }, + { + id: 'rule.mitre.id', + render: (value: any) => { + const destURL = getCore().application.getUrlForApp('mitre-attack', { + path: `#/overview/?tab=mitre&tabView=intelligence&tabRedirect=techniques&idToRedirect=${value}`, + }); + return ( + + + {value} + + + ); + }, + }, + { + id: 'rule.mitre.tactic', + }, + { + id: 'rule.description', + }, + { + id: 'rule.level', + }, + { + id: 'rule.id', + render: (value: any) => { + const destURL = getCore().application.getUrlForApp('rules', { + path: `manager/?tab=ruleset&redirectRule=${value}`, + }); + return ( + + + {value} + + + ); + }, + }, +]; diff --git a/plugins/main/public/components/visualize/wz-visualize.js b/plugins/main/public/components/visualize/wz-visualize.js index 4f4f768233..ef3fd2c857 100644 --- a/plugins/main/public/components/visualize/wz-visualize.js +++ b/plugins/main/public/components/visualize/wz-visualize.js @@ -337,7 +337,7 @@ export const WzVisualize = compose( className='embPanel__header' >

- Security Alerts + Security Alerts table