From 11cb294126e3d711ef4008dd8dcc3f769262b6f3 Mon Sep 17 00:00:00 2001 From: Maximiliano Ibarra <6089438+Machi3mfl@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:08:48 -0300 Subject: [PATCH] Add vulnerability detection card in agent overview (#7085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add top packages component * Add vuls severity stat component * Create vuls panel * Add vuls panel in agent overview * Update CHANGELOG * Apply prettier * Fix responsive behavior * Apply prettier * Change vuls panel style * Use vuls data source in agent overview panel * Move vuls panel * Change redirect in hoc * Remove unnecessary redirect * Add HOC to create pattern if not exist * Apply prettier * Remove unused imports Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * Remove unused imports Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * Remove unused code * Apply prettier * Resolve conflicts in agents-welcome * Change agents management menu icon --------- Co-authored-by: Chantal Belén kelm <99441266+chantal-kelm@users.noreply.github.com> Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> Co-authored-by: Federico Rodriguez --- CHANGELOG.md | 1 + .../common/welcome/agents-welcome.js | 134 +++++------- .../fim_events_table/fim_events_table.tsx | 2 +- .../common/welcome/components/index.ts | 1 + .../components/top_packages_table/index.ts | 15 ++ .../top_packages_table/top_packages_table.tsx | 92 ++++++++ .../vuls_panel/vuls_welcome_panel.tsx | 202 ++++++++++++++++++ .../vuls_severity_stat/vuls_severity_stat.tsx | 31 +++ ...e-vulnerabilities-states-index-pattern.tsx | 12 +- .../last-alerts-stat/last-alerts-stat.tsx | 38 +++- plugins/main/public/utils/applications.ts | 2 +- 11 files changed, 429 insertions(+), 101 deletions(-) create mode 100644 plugins/main/public/components/common/welcome/components/top_packages_table/index.ts create mode 100644 plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx create mode 100644 plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx create mode 100644 plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index a58c302796..7b45e407e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.10.0 - Added sample data for YARA [#6964](https://github.com/wazuh/wazuh-dashboard-plugins/issues/6964) - Added a custom filter and visualization for vulnerability.under_evaluation field [#6968](https://github.com/wazuh/wazuh-dashboard-plugins/issues/6968) [#7044](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7044) [#7046](https://github.com/wazuh/wazuh-dashboard-plugins/issues/7046) +- Add vulnerabilities card to agent details page [#7058](https://github.com/wazuh/wazuh-dashboard-plugins/issues/7058) - Added an "Agents management" menu and moved the sections: "Endpoint Groups" and "Endpoint Summary" which changed its name to "Summary".[#7112](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7112) - Added ability to filter from File Integrity Monitoring registry inventory [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) - Added new field columns and ability to select the visible fields in the File Integrity Monitoring Files and Registry tables [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) diff --git a/plugins/main/public/components/common/welcome/agents-welcome.js b/plugins/main/public/components/common/welcome/agents-welcome.js index d1bd5d6c90..61d41e9a2e 100644 --- a/plugins/main/public/components/common/welcome/agents-welcome.js +++ b/plugins/main/public/components/common/welcome/agents-welcome.js @@ -58,6 +58,7 @@ import { EventsCount } from './dashboard/events-count'; import { IntlProvider } from 'react-intl'; import { ButtonExploreAgent } from '../../wz-agent-selector/button-explore-agent'; import NavigationService from '../../../react-services/navigation-service'; +import VulsPanel from './components/vuls_panel/vuls_welcome_panel'; import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs'; export const AgentsWelcome = compose( @@ -391,8 +392,8 @@ export const AgentsWelcome = compose( renderMitrePanel() { return ( - - + +

@@ -452,6 +453,8 @@ export const AgentsWelcome = compose( render() { const title = this.renderTitle(); + const responsiveGroupDirection = + this.state.widthWindow < 1150 ? 'column' : 'row'; return ( @@ -459,89 +462,64 @@ export const AgentsWelcome = compose(
{title}
- - -
+
+ + -
- - - - {' '} - {/* TODO: Replace with SearchBar and replace implementation to get the time range in AgentView component*/} - {}} /> - - - {(this.state.widthWindow < 1150 && ( - - - - {this.renderMitrePanel()} - - {this.renderCompliancePanel()} - - - - - - - - - {' '} - {/* Events count evolution */} - {this.renderEventCountVisualization()} - - - - - {this.renderSCALastScan()} - - - )) || ( - - - - - - {this.renderMitrePanel()} - + + + + {' '} + {/* TODO: Replace with SearchBar and replace implementation to get the time range in AgentView component*/} + {}} /> + + + + + {this.renderEventCountVisualization()} + + + + + {this.renderMitrePanel()} + + {this.renderCompliancePanel()} - - - - - - - - {' '} - {/* Events count evolution */} - {this.renderEventCountVisualization()} - - {this.renderSCALastScan()} - - - )} - - + + + + + + + + + + {this.renderSCALastScan()} + + + + + + + +
); diff --git a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx index f2ec64eb15..7eb4d26d5b 100644 --- a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx +++ b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx @@ -39,7 +39,7 @@ export function FimEventsTable({ agent }) { - +

FIM: Recent events

diff --git a/plugins/main/public/components/common/welcome/components/index.ts b/plugins/main/public/components/common/welcome/components/index.ts index e6b4fae312..f40a1a90d0 100644 --- a/plugins/main/public/components/common/welcome/components/index.ts +++ b/plugins/main/public/components/common/welcome/components/index.ts @@ -16,3 +16,4 @@ export { FimEventsTable, useTimeFilter } from './fim_events_table'; export { ScaScan } from './sca_scan'; export { MitreTopTactics } from './mitre_top'; export { RequirementVis } from './requirement_vis'; +export { VulsTopPackageTable } from './top_packages_table'; diff --git a/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts b/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts new file mode 100644 index 0000000000..4fd81bc185 --- /dev/null +++ b/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts @@ -0,0 +1,15 @@ +/* + * Wazuh app - React component building the welcome screen of an agent. + * version, OS, registration date, last keep alive. + * + * Copyright (C) 2015-2022 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +export { VulsTopPackageTable, useTimeFilter } from './top_packages_table'; diff --git a/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx b/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx new file mode 100644 index 0000000000..7ae98623c7 --- /dev/null +++ b/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx @@ -0,0 +1,92 @@ +/* + * Wazuh app - React component building the welcome screen of an agent. + * version, OS, registration date, last keep alive. + * + * Copyright (C) 2015-2022 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import React, { useState, useEffect } from 'react'; +import { + EuiBasicTable, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, + EuiFlexGroup, +} from '@elastic/eui'; +// @ts-ignore +import { getDataPlugin } from '../../../../../kibana-services'; +import { vulnerabilityDetection } from '../../../../../utils/applications'; +import { + PatternDataSourceFilterManager, + FILTER_OPERATOR, +} from '../../../data-source'; +import { WzLink } from '../../../../../components/wz-link/wz-link'; + +export function VulsTopPackageTable({ agentId, items, indexPatternId }) { + const [sort, setSort] = useState({ + field: 'doc_count', + direction: 'desc', + }); + + const columns = [ + { + field: 'key', + name: 'Package', + sortable: true, + render: field => ( + + {field} + + ), + }, + { + field: 'doc_count', + name: 'Count', + sortable: true, + truncateText: true, + width: '100px', + }, + ]; + + return ( + + + + +

Top 5 Packages

+
+
+
+ + setSort(e.sort)} + itemId='top-packages-table' + noItemsMessage='No recent events' + /> +
+ ); +} diff --git a/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx b/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx new file mode 100644 index 0000000000..fcd13c4d8c --- /dev/null +++ b/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx @@ -0,0 +1,202 @@ +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiToolTip, + EuiButtonIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import React, { Fragment, useEffect, useState } from 'react'; +import { VulsTopPackageTable } from '../top_packages_table'; +import VulsSeverityStat from '../vuls_severity_stat/vuls_severity_stat'; +import { + PatternDataSourceFilterManager, + PatternDataSource, + tParsedIndexPattern, + useDataSource, + FILTER_OPERATOR, + VulnerabilitiesDataSourceRepository, + VulnerabilitiesDataSource, +} from '../../../data-source'; +import { severities } from '../../../../../controllers/overview/components/last-alerts-stat/last-alerts-stat'; +import { getCore } from '../../../../../kibana-services'; +import { RedirectAppLinks } from '../../../../../../../../src/plugins/opensearch_dashboards_react/public'; +import { vulnerabilityDetection } from '../../../../../utils/applications'; +import NavigationService from '../../../../../react-services/navigation-service'; +import { WzLink } from '../../../../../components/wz-link/wz-link'; +import { withErrorBoundary } from '../../../../common/hocs'; +import { compose } from 'redux'; +import { withVulnerabilitiesStateDataSource } from '../../../../../components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern'; + +const VulsPanelContent = ({ agent }) => { + const { + dataSource, + isLoading: isDataSourceLoading, + fetchData, + } = useDataSource({ + DataSource: VulnerabilitiesDataSource, + repository: new VulnerabilitiesDataSourceRepository(), + }); + + const [isLoading, setIsLoading] = useState(true); + const [severityStats, setSeverityStats] = useState(null); + const [topPackagesData, setTopPackagesData] = useState([]); + + const fetchSeverityStatsData = async () => { + const data = await fetchData({ + aggs: { + severity: { + terms: { + field: 'vulnerability.severity', + size: 5, + order: { + _count: 'desc', + }, + }, + }, + }, + }); + setSeverityStats(data.aggregations.severity.buckets); + }; + + const fetchTopPackagesData = async () => { + fetchData({ + aggs: { + package: { + terms: { + field: 'package.name', + size: 5, + order: { + _count: 'desc', + }, + }, + }, + }, + }).then(results => { + setTopPackagesData(results.aggregations.package.buckets); + }); + }; + + useEffect(() => { + if (isDataSourceLoading) { + return; + } + setIsLoading(true); + Promise.all([fetchSeverityStatsData(), fetchTopPackagesData()]) + .catch(error => { + console.error(error); + }) + .finally(() => { + setIsLoading(false); + }); + }, [isDataSourceLoading, agent.id]); + + const getSeverityValue = severity => { + const value = + severityStats?.find(v => v.key.toUpperCase() === severity.toUpperCase()) + ?.doc_count || '0'; + return value ? `${value} ${severity}` : '0'; + }; + + const renderSeverityStats = (severity, index) => { + const severityLabel = severities[severity].label; + const severityColor = severities[severity].color; + return ( + + + + + + + + + + + + ); + }; + + return ( + + + + {Object.keys(severities).reverse().map(renderSeverityStats)} + + + + + + + ); +}; + +const PanelWithVulnerabilitiesState = compose( + withErrorBoundary, + withVulnerabilitiesStateDataSource, +)(VulsPanelContent); + +const VulsPanel = ({ agent }) => { + return ( + + + + + + +

Vulnerability Detection

+
+
+ + + + + + + +
+
+ + +
+
+ ); +}; + +export default VulsPanel; diff --git a/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx b/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx new file mode 100644 index 0000000000..8796d682d2 --- /dev/null +++ b/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { EuiStat, EuiStatProps } from '@elastic/eui'; + +type VulsSeverityStatProps = { + value: string; + color: string; + textAlign?: EuiStatProps['textAlign']; + statElement?: EuiStatProps['descriptionElement']; + isLoading: boolean; +}; + +export default function VulsSeverityStat({ + value, + color, + textAlign = 'center', + statElement = 'h2', + isLoading, +}: VulsSeverityStatProps) { + return ( + + ); +} diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx index 6f0ed611c8..8468fa3f7a 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -24,7 +24,7 @@ export async function validateVulnerabilitiesStateDataSources({ const existIndexPattern = await existsIndexPattern(indexPatternID); const indexPattern = existIndexPattern; - // If the idnex pattern does not exist, then check the existence of index + // If the index pattern does not exist, then check the existence of index if (existIndexPattern?.error?.statusCode === HTTP_STATUS_CODES.NOT_FOUND) { // Check the existence of indices const { exist, fields } = await existsIndices(indexPatternID); @@ -41,7 +41,7 @@ export async function validateVulnerabilitiesStateDataSources({ }, }; } - // If the some index match the index pattern, then create the index pattern + // If some index matches the index pattern, then create the index pattern const resultCreateIndexPattern = await createIndexPattern( indexPatternID, fields, @@ -57,14 +57,6 @@ export async function validateVulnerabilitiesStateDataSources({ }, }; } - /* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should - redirects to the Dashboard tab. We want to redirect to this view, because we need the - component is visible (visualizations) to ensure the process that defines the filters for the - Events tab is run when the Dashboard component is unmounted. This workaround solves a - problem in the Events tabs related there are no implicit filters when accessing if the HOC - that protect the view is passed. - */ - NavigationService.getInstance().navigateToApp(vulnerabilityDetection.id); } return { ok: false, diff --git a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx index 4f51cf8600..cf9258ca81 100644 --- a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx +++ b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx @@ -5,6 +5,7 @@ import { EuiLink, EuiToolTip, EuiText, + EuiStatProps, } from '@elastic/eui'; import { getLast24HoursAlerts } from './last-alerts-service'; import { UI_COLOR_STATUS } from '../../../../../common/constants'; @@ -22,7 +23,7 @@ import { type SeverityKey = 'low' | 'medium' | 'high' | 'critical'; -const severities = { +export const severities = { low: { label: 'Low', color: UI_COLOR_STATUS.success, @@ -59,8 +60,14 @@ const severities = { export function LastAlertsStat({ severity: severityKey, + hideBottomText, + direction = 'row', + textAlign = 'center', }: { severity: SeverityKey; + hideBottomText?: boolean; + direction: 'row' | 'column'; + textAlign?: EuiStatProps['textAlign']; }) { const [countLastAlerts, setCountLastAlerts] = useState(null); const [discoverLocation, setDiscoverLocation] = useState(''); @@ -117,6 +124,13 @@ export function LastAlertsStat({ getCountLastAlerts(); }, []); + const statDescription = + direction === 'row' ? `${severity.label} severity` : ''; + const statValue = + direction === 'row' + ? `${countLastAlerts ?? '-'}` + : ` ${countLastAlerts ?? '-'} ${severity.label}`; + return ( @@ -141,22 +155,24 @@ export function LastAlertsStat({ }} href={discoverLocation} > - {countLastAlerts ?? '-'} + {statValue} } - description={`${severity.label} severity`} + description={statDescription} descriptionElement='h3' titleColor={severity.color} - textAlign='center' + textAlign={textAlign} /> - - {'Rule level ' + - ruleLevelRange.minRuleLevel + - (ruleLevelRange.maxRuleLevel - ? ' to ' + ruleLevelRange.maxRuleLevel - : ' or higher')} - + {hideBottomText ? null : ( + + {'Rule level ' + + ruleLevelRange.minRuleLevel + + (ruleLevelRange.maxRuleLevel + ? ' to ' + ruleLevelRange.maxRuleLevel + : ' or higher')} + + )} ); diff --git a/plugins/main/public/utils/applications.ts b/plugins/main/public/utils/applications.ts index 9b07810efd..0351a414f6 100644 --- a/plugins/main/public/utils/applications.ts +++ b/plugins/main/public/utils/applications.ts @@ -904,7 +904,7 @@ export const Categories = [ defaultMessage: 'Agents management', }), order: 600, - euiIconType: 'indexRollupApp', + euiIconType: 'graphApp', }, { id: 'wz-category-server-management',