diff --git a/.gitignore b/.gitignore index f86051e821..08fe2e980f 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,7 @@ public/assets/custom/* # Mac files .DS_Store + +#Vulnerabilities events injector config and data +scripts/vulnerabilities-events-injector/DIS_Settings.json +scripts/vulnerabilities-events-injector/generatedData.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b09a7c01f5..3b3cdf84b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,10 @@ All notable changes to the Wazuh app project will be documented in this file. - Added the ability to check if there are available updates from the UI. [#6093](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6093) - Added remember server address check [#5791](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5791) - Added the ssl_agent_ca configuration to the SSL Settings form [#6083](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6083) -- Added global vulnerabilities dashboards [#5896](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5896) +- Added global vulnerabilities dashboards [#5896](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5896) [#6179](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6179) [#6173](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6173) [#6147](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6147) - Added an agent selector to the IT Hygiene application [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) - Added query results limit when the search exceed 10000 hits [#6106](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6106) +- Added a redirection button to Endpoint Summary from IT Hygiene application [6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) ### Changed @@ -38,11 +39,16 @@ All notable changes to the Wazuh app project will be documented in this file. - Fixed a problem with the agent menu header when the side menu is docked [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) - Fixed how the query filters apply on the Security Alerts table [#6102](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6102) +- Fixed exception in IT-Hygiene when an agent doesn't have policies [#6177](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6177) +- Fixed exception in Inventory when agents don't have S.O. information [#6177](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6177) +- Fixed pinned agent state in URL [#6177](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6177) ### Removed - Removed the `disabled_roles` and `customization.logo.sidebar` settings [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) - Removed the ability to configure the visibility of modules and removed `extensions.*` settings [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) +- Removed the application menu in the IT Hygiene application [6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) +- Removed the implicit filter of WQL language of the search bar UI [#6174](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6174) ## Wazuh v4.7.1 - OpenSearch Dashboards 2.8.0 - Revision 01 diff --git a/docker/imposter/manager/version/check.json b/docker/imposter/manager/version/check.json index 82c37160f5..bcba3a69b7 100644 --- a/docker/imposter/manager/version/check.json +++ b/docker/imposter/manager/version/check.json @@ -1,5 +1,6 @@ { "data": { + "uuid": "7f828fd6-ef68-4656-b363-247b5861b84c", "last_check_date": "2023-10-04T14:52:07.319561Z", "current_version": "v4.8.0", "update_check": true, diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js index fcfd359454..4e0f80afc0 100644 --- a/plugins/main/common/config-equivalences.js +++ b/plugins/main/common/config-equivalences.js @@ -60,6 +60,7 @@ export const configEquivalences = { 'Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.', 'vulnerabilities.pattern': 'Default index pattern to use for vulnerabilities.', + 'fim.pattern': 'Default index pattern to use for fim.', }; export const nameEquivalence = { @@ -98,6 +99,8 @@ export const nameEquivalence = { 'alerts.sample.prefix': 'Sample alerts prefix', 'vulnerabilities.pattern': 'Index pattern', 'checks.vulnerabilities.pattern': 'Vulnerabilities index pattern', + 'fim.pattern': 'Index pattern', + 'checks.fim.pattern': 'Fim index pattern', }; const HEALTH_CHECK = 'Health Check'; @@ -106,6 +109,7 @@ const SECURITY = 'Security'; const MONITORING = 'Monitoring'; const STATISTICS = 'Statistics'; const VULNERABILITIES = 'Vulnerabilities'; +const FIM = 'Fim'; const CUSTOMIZATION = 'Logo Customization'; export const categoriesNames = [ HEALTH_CHECK, @@ -114,6 +118,7 @@ export const categoriesNames = [ MONITORING, STATISTICS, VULNERABILITIES, + FIM, CUSTOMIZATION, ]; @@ -153,6 +158,8 @@ export const categoriesEquivalence = { 'alerts.sample.prefix': GENERAL, 'vulnerabilities.pattern': VULNERABILITIES, 'checks.vulnerabilities.pattern': HEALTH_CHECK, + 'fim.pattern': FIM, + 'checks.fim.pattern': HEALTH_CHECK, }; const TEXT = 'text'; @@ -226,4 +233,6 @@ export const formEquivalence = { 'alerts.sample.prefix': { type: TEXT }, 'vulnerabilities.pattern': { type: TEXT }, 'checks.vulnerabilities.pattern': { type: BOOLEAN }, + 'fim.pattern': { type: TEXT }, + 'checks.fim.pattern': { type: BOOLEAN }, }; diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 4949a30685..b2cb176ccc 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -50,6 +50,11 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; // Wazuh vulnerabilities export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; +export const WAZUH_INDEX_TYPE_VULNERABILITIES = 'vulnerabilities'; + +// Wazuh fim +export const WAZUH_FIM_PATTERN = 'wazuh-alerts-*'; +export const WAZUH_INDEX_TYPE_FIM = 'fim'; // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; @@ -861,6 +866,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, + 'checks.fim.pattern': { + title: 'Fim index pattern', + description: + 'Enable or disable the fim index pattern health check when opening the app.', + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + }, + }, + }, + uiFormTransformChangedInputValue: function ( + value: boolean | string, + ): boolean { + return Boolean(value); + }, + validate: SettingsValidator.isBoolean, + validateBackend: function (schema) { + return schema.boolean(); + }, + }, 'cron.prefix': { title: 'Cron prefix', description: 'Define the index prefix of predefined jobs.', diff --git a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap index f515d70f35..227faac4a4 100644 --- a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap +++ b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap @@ -102,8 +102,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` style="margin-right:4px;margin-top:0" >
(a.label > b.label ? 1 : -1); + +export const NetworkInterfacesTable = ({ agent }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return netifaceColumns + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/netiface`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); +}; diff --git a/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx new file mode 100644 index 0000000000..0d1f43aa73 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiPanel } from '@elastic/eui'; +import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants'; +import { TableWzAPI } from '../../../common/tables'; +import { WzRequest } from '../../../../react-services'; +import { get as getLodash } from 'lodash'; +import { portsColumns } from '../columns'; +import { withSOPlatformGuard } from './with-so-platform-guard'; + +const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); + +export const NetworkPortsTable = withSOPlatformGuard( + ({ agent, soPlatform }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return portsColumns[soPlatform] + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/ports`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); + }, +); diff --git a/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx new file mode 100644 index 0000000000..a6436086e7 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants'; +import { TableWzAPI } from '../../../common/tables'; +import { WzRequest } from '../../../../react-services'; +import { get as getLodash } from 'lodash'; +import { netaddrColumns } from '../columns'; + +const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); + +export const NetworkSettingsTable = ({ agent }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return netaddrColumns + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/netaddr`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); +}; diff --git a/plugins/main/public/components/agents/syscollector/components/packages-table.tsx b/plugins/main/public/components/agents/syscollector/components/packages-table.tsx new file mode 100644 index 0000000000..087aae5ba5 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/packages-table.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { EuiIcon, EuiPanel } from '@elastic/eui'; +import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants'; +import { TableWzAPI } from '../../../common/tables'; +import { WzRequest } from '../../../../react-services'; +import { get as getLodash } from 'lodash'; +import { packagesColumns } from '../columns'; +import { withSOPlatformGuard } from './with-so-platform-guard'; + +const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); + +export const PackagesTable = withSOPlatformGuard(({ agent, soPlatform }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return packagesColumns[soPlatform] + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/packages`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); +}); diff --git a/plugins/main/public/components/agents/syscollector/components/processes-table.tsx b/plugins/main/public/components/agents/syscollector/components/processes-table.tsx new file mode 100644 index 0000000000..ea571673c3 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/processes-table.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { EuiIcon, EuiPanel } from '@elastic/eui'; +import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants'; +import { TableWzAPI } from '../../../common/tables'; +import { WzRequest } from '../../../../react-services'; +import { get as getLodash } from 'lodash'; +import { processColumns } from '../columns'; +import { withSOPlatformGuard } from './with-so-platform-guard'; + +const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); + +export const ProcessesTable = withSOPlatformGuard(({ agent, soPlatform }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return processColumns[soPlatform] + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/processes`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); +}); diff --git a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx new file mode 100644 index 0000000000..585726b3a8 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants'; +import { TableWzAPI } from '../../../common/tables'; +import { WzRequest } from '../../../../react-services'; +import { get as getLodash } from 'lodash'; +import { windowsUpdatesColumns } from '../columns'; + +const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); + +export const WindowsUpdatesTable = ({ agent }) => { + return ( + + field) + .join(',')}`} + searchTable + downloadCsv + showReload + tablePageSizeOptions={[10, 25, 50, 100]} + searchBarWQL={{ + suggestions: { + field(currentValue) { + return windowsUpdatesColumns + .map(item => ({ + label: item.field, + description: `filter by ${item.name}`, + })) + .sort(sortFieldSuggestion); + }, + value: async (currentValue, { field }) => { + try { + const response = await WzRequest.apiReq( + 'GET', + `/syscollector/${agent.id}/hotfixes`, + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { q: `${field}~${currentValue}` } + : {}), + }, + }, + ); + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } catch (error) { + return []; + } + }, + }, + }} + tableProps={{ + tableLayout: 'auto', + }} + /> + + ); +}; diff --git a/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx b/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx new file mode 100644 index 0000000000..bf3efd3b17 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiPanel } from '@elastic/eui'; + +export const withSOPlatformGuard = WrappedComponent => props => { + const { soPlatform } = props; + if (!soPlatform) { + return ( + + + +
+ + Not enough hardware or operating system information +
+
+
+
+ ); + } + return ; +}; diff --git a/plugins/main/public/components/agents/syscollector/inventory.scss b/plugins/main/public/components/agents/syscollector/inventory.scss new file mode 100644 index 0000000000..e00bbb455f --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/inventory.scss @@ -0,0 +1,3 @@ +.wz-agent-inventory-panel { + margin: '12px 16px 12px 16px'; +} diff --git a/plugins/main/public/components/agents/syscollector/inventory.tsx b/plugins/main/public/components/agents/syscollector/inventory.tsx index f6060112e3..57df7132ee 100644 --- a/plugins/main/public/components/agents/syscollector/inventory.tsx +++ b/plugins/main/public/components/agents/syscollector/inventory.tsx @@ -11,28 +11,21 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { InventoryMetrics } from './components/syscollector-metrics'; -import { - netaddrColumns, - netifaceColumns, - processColumns, - portsColumns, - packagesColumns, - windowsUpdatesColumns, -} from './columns'; -import { - API_NAME_AGENT_STATUS, - SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, -} from '../../../../common/constants'; -import { TableWzAPI } from '../../common/tables'; -import { WzRequest } from '../../../react-services'; -import { get as getLodash } from 'lodash'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; import { compose } from 'redux'; import { withGuard } from '../../common/hocs'; import { PromptAgentNeverConnected } from '../prompts'; - -const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); +import { + NetworkInterfacesTable, + NetworkPortsTable, + NetworkSettingsTable, + WindowsUpdatesTable, + ProcessesTable, + PackagesTable, +} from './components'; +import './inventory.scss'; export const SyscollectorInventory = compose( withGuard( @@ -72,359 +65,33 @@ export const SyscollectorInventory = compose( - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return netifaceColumns - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/netiface`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map(item => ({ - label: getLodash(item, field), - })); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - + - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return portsColumns[soPlatform] - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/ports`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map(item => ({ - label: getLodash(item, field), - })); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - + - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return netaddrColumns - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/netaddr`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map(item => ({ - label: getLodash(item, field), - })); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - + {agent && agent.os && agent.os.platform === 'windows' && ( - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return windowsUpdatesColumns - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/hotfixes`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map( - item => ({ - label: getLodash(item, field), - }), - ); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - + )} - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return packagesColumns[soPlatform] - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/packages`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map(item => ({ - label: getLodash(item, field), - })); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - + - - field) - .join(',')}`} - searchTable - downloadCsv - showReload - tablePageSizeOptions={[10, 25, 50, 100]} - searchBarWQL={{ - suggestions: { - field(currentValue) { - return processColumns[soPlatform] - .map(item => ({ - label: item.field, - description: `filter by ${item.name}`, - })) - .sort(sortFieldSuggestion); - }, - value: async (currentValue, { field }) => { - try { - const response = await WzRequest.apiReq( - 'GET', - `/syscollector/${agent.id}/processes`, - { - params: { - distinct: true, - limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, - select: field, - sort: `+${field}`, - ...(currentValue - ? { q: `${field}~${currentValue}` } - : {}), - }, - }, - ); - return response?.data?.data.affected_items.map(item => ({ - label: getLodash(item, field), - })); - } catch (error) { - return []; - } - }, - }, - }} - tableProps={{ - tableLayout: 'auto', - }} - /> - +
diff --git a/plugins/main/public/components/common/hooks/use-app-config.ts b/plugins/main/public/components/common/hooks/use-app-config.ts index a2a3e93d4b..3e3dae04c9 100644 --- a/plugins/main/public/components/common/hooks/use-app-config.ts +++ b/plugins/main/public/components/common/hooks/use-app-config.ts @@ -15,5 +15,10 @@ import { useSelector } from 'react-redux'; export const useAppConfig = () => { const appConfig = useSelector((state: AppRootState) => state.appConfig); + console.log(appConfig, 'appConfig in useAppConfig'); + console.log(appConfig.data['fim.pattern'], "appConfig.data['fim.pattern']"); + + const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern']; + console.log(FIM_INDEX_PATTERN_ID, 'FIM_INDEX_PATTERN_ID in useAppConfig'); return appConfig; -} +}; diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js index 24b3b2b421..4d94450889 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.js @@ -11,9 +11,7 @@ */ import { Dashboard } from './dashboard'; import { Events } from './events'; -import { MainFim } from '../../agents/fim'; import { MainSca } from '../../agents/sca'; -import { MainVuls } from '../../agents/vuls'; import { MainMitre } from './main-mitre'; import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; import { ComplianceTable } from '../../overview/compliance-table'; @@ -21,8 +19,11 @@ import ButtonModuleExploreAgent from '../../../controllers/overview/components/o import { ButtonModuleGenerateReport } from '../modules/buttons'; import { OfficePanel } from '../../overview/office-panel'; import { GitHubPanel } from '../../overview/github-panel'; -import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities' -import { withModuleNotForAgent, withModuleTabLoader } from '../hocs'; +import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'; +import { DashboardFim } from '../../overview/fim/dashboard/overview/dashboard'; +import { InventoryFim } from '../../overview/fim/inventory/inventory'; + +import { withModuleNotForAgent } from '../hocs'; const DashboardTab = { id: 'dashboard', @@ -56,17 +57,41 @@ export const ModulesDefaults = { fim: { init: 'dashboard', tabs: [ - DashboardTab, + { + id: 'dashboard', + name: 'Dashboard', + component: DashboardFim, + }, { id: 'inventory', name: 'Inventory', - buttons: [ButtonModuleExploreAgent], - component: MainFim, + component: InventoryFim, }, EventsTab, ], + buttons: ['settings'], availableFor: ['manager', 'agent'], }, + + // vuls: { + // init: 'dashboard', + // tabs: [ + // { + // id: 'dashboard', + // name: 'Dashboard', + // component: withModuleNotForAgent(DashboardVuls), + // }, + // { + // id: 'inventory', + // name: 'Inventory', + // component: withModuleNotForAgent(InventoryVuls), + // }, + // EventsTab, + // ], + // buttons: ['settings'], + // availableFor: ['manager'], + // }, + aws: { init: 'dashboard', tabs: [DashboardTab, EventsTab], diff --git a/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx b/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx index c910eebee8..0b73f1080d 100644 --- a/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx +++ b/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx @@ -100,7 +100,7 @@ export class GroupTruncate extends React.Component { ); } - renderGroups(groups) { + renderGroups(groups = []) { const { length } = this.props; let auxGroups: Array = []; let tooltipGroups: Array = []; diff --git a/plugins/main/public/components/common/welcome/agents-info.js b/plugins/main/public/components/common/welcome/agents-info.js index 44aaa139dc..dd49faecab 100644 --- a/plugins/main/public/components/common/welcome/agents-info.js +++ b/plugins/main/public/components/common/welcome/agents-info.js @@ -110,7 +110,8 @@ export class AgentInfo extends Component { { - let menuSize; - if (this.state.isLocked) { - menuSize = window.innerWidth - this.offset - this.sidebarSizeDefault; - } else { - menuSize = window.innerWidth - this.offset; - } - let maxModules = 5; - if (menuSize > 1400) { - maxModules = 5; - } else { - if (menuSize > 1250) { - maxModules = 4; - } else { - if (menuSize > 1100) { - maxModules = 3; - } else { - if (menuSize > 900) { - maxModules = 2; - } else { - maxModules = 1; - if (menuSize < 750) { - maxModules = null; - } - } - } - } - } - - this.setState({ maxModules: maxModules, widthWindow: window.innerWidth }); + this.setState({ widthWindow: window.innerWidth }); }; /* TODO: we should to create a unique Explore agent button instead @@ -187,12 +143,15 @@ export const AgentsWelcome = compose( getDataPlugin().query.filterManager.setFilters(agentFilters); } + clearAgentInUrl() { + this.location.search('agent', null); + } + async componentDidMount() { this._isMount = true; /* WORKAROUND: ensure the $scope.agent is synced with the agent stored in Redux (this.props.agent). See agents.js controller. */ this.props.setAgent(this.props.agent); - this.updatePinnedApplications(); this.updateWidth(); const tabVisualizations = new TabVisualizations(); tabVisualizations.removeAll(); @@ -204,12 +163,11 @@ export const AgentsWelcome = compose( const $injector = getAngularModule().$injector; this.drawerLokedSubscribtion = getChrome() .getIsNavDrawerLocked$() - .subscribe(isLocked => { - this.setState({ isLocked }, () => { - this.updateWidth(); - }); + .subscribe(() => { + this.updateWidth(); }); this.router = $injector.get('$route'); + this.location = $injector.get('$location'); window.addEventListener('resize', this.updateWidth); //eslint-disable-line await VisFactoryHandler.buildAgentsVisualizations( filterHandler, @@ -230,117 +188,34 @@ export const AgentsWelcome = compose( this.drawerLokedSubscribtion?.unsubscribe(); } - updatePinnedApplications(applications) { - let pinnedApplications; - - if (applications) { - pinnedApplications = applications; - } else { - pinnedApplications = window.localStorage.getItem( - 'wz-menu-agent-apps-pinned', - ) - ? JSON.parse(window.localStorage.getItem('wz-menu-agent-apps-pinned')) - : [ - // Default pinned applications - threatHunting.id, - fileIntegrityMonitoring.id, - configurationAssessment.id, - vulnerabilityDetection.id, - mitreAttack.id, - ]; - } - - // Ensure the pinned applications are supported - pinnedApplications = pinnedApplications.filter(pinnedApplication => - Applications.some(({ id }) => id === pinnedApplication), - ); - - window.localStorage.setItem( - 'wz-menu-agent-apps-pinned', - JSON.stringify(pinnedApplications), + renderEndpointsSummaryButton() { + const application = Applications.find( + ({ id }) => id === 'endpoints-summary', ); - this.setState({ menuAgent: pinnedApplications }); - } - - renderModules() { return ( - - {this.state.menuAgent.map((applicationId, i) => { - const moduleID = Object.keys(WAZUH_MODULES).find( - key => WAZUH_MODULES[key]?.appId === applicationId, - ).appId; - if ( - i < this.state.maxModules && - hasAgentSupportModule(this.props.agent, moduleID) - ) { - return ( - - - - - { - Applications.find(({ id }) => id === applicationId) - .title - } -   - - - - - ); - } - })} - - - this.setState({ switchModule: !this.state.switchModule }) - } - > - More... - - } - isOpen={this.state.switchModule} - closePopover={() => this.setState({ switchModule: false })} - repositionOnScroll={false} - anchorPosition='downCenter' - > -
- -
- - this.updatePinnedApplications(applications) - } - closePopover={() => { - this.setState({ switchModule: false }); - }} - switchTab={module => this.props.switchTab(module)} - > -
-
-
-
-
-
+ + + {application.title} + + ); } renderTitle() { const notNeedStatus = true; const thereAreAgentSelected = Boolean(this.props.agent?.id); + // Calculate if the header buttons should display the name or only the icon to be responsive + return ( - {(this.state.maxModules !== null && this.renderModules()) || ( - - - this.setState({ - switchModule: !this.state.switchModule, - }) - } - > - Applications - - } - isOpen={this.state.switchModule} - closePopover={() => this.setState({ switchModule: false })} - repositionOnScroll={false} - anchorPosition='downCenter' - > -
- -
- - this.updatePinnedApplications(applications) - } - closePopover={() => { - this.setState({ switchModule: false }); - }} - switchTab={module => this.props.switchTab(module)} - > -
-
-
-
-
- )} + + {this.renderEndpointsSummaryButton()} +
@@ -417,6 +254,7 @@ export const AgentsWelcome = compose( className='wz-unpin-agent' iconType='pinFilled' onClick={() => { + this.clearAgentInUrl(); this.props.updateCurrentAgentData({}); this.removeAgentsFilter(); }} @@ -433,13 +271,14 @@ export const AgentsWelcome = compose( onClick={() => this.props.switchTab('syscollector', notNeedStatus) } - tooltip={ - this.state.maxModules === null - ? { position: 'bottom', content: 'Inventory data' } - : undefined - } + className='wz-it-hygiene-header-button' + tooltip={{ + position: 'bottom', + content: 'Inventory data', + className: 'wz-it-hygiene-header-button-tooltip', + }} > - {this.state.maxModules !== null ? 'Inventory data' : ''} + Inventory data @@ -447,13 +286,14 @@ export const AgentsWelcome = compose( buttonType='empty' iconType='stats' onClick={() => this.props.switchTab('stats', notNeedStatus)} - tooltip={ - this.state.maxModules === null - ? { position: 'bottom', content: 'Stats' } - : undefined - } + className='wz-it-hygiene-header-button' + tooltip={{ + position: 'bottom', + content: 'Stats', + className: 'wz-it-hygiene-header-button-tooltip', + }} > - {this.state.maxModules !== null ? 'Stats' : ''} + Stats @@ -463,13 +303,14 @@ export const AgentsWelcome = compose( onClick={() => this.props.switchTab('configuration', notNeedStatus) } - tooltip={ - this.state.maxModules === null - ? { position: 'bottom', content: 'Configuration' } - : undefined - } + className='wz-it-hygiene-header-button' + tooltip={{ + position: 'bottom', + content: 'Configuration', + className: 'wz-it-hygiene-header-button-tooltip', + }} > - {this.state.maxModules !== null ? 'Configuration' : ''} + Configuration
diff --git a/plugins/main/public/components/common/welcome/components/agent-sections.ts b/plugins/main/public/components/common/welcome/components/agent-sections.ts deleted file mode 100644 index 3278e60914..0000000000 --- a/plugins/main/public/components/common/welcome/components/agent-sections.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Wazuh app - Build all sections for MenuAgent. - * 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 { WAZUH_MODULES_ID } from '../../../../../common/constants'; - -export const getAgentSections = (menuAgent) => { - return { - securityInformation: { - id: 'securityInformation', - text: 'Security information management', - isTitle: true, - }, - auditing: { - id: 'auditing', - text: 'Auditing and Policy Monitoring', - isTitle: true, - }, - threatDetection: { - id: 'threatDetection', - text: 'Threat detection and response', - isTitle: true, - }, - regulatoryCompliance: { - id: 'regulatoryCompliance', - text: 'Regulatory Compliance', - isTitle: true, - }, - general: { - id: WAZUH_MODULES_ID.SECURITY_EVENTS, - text: 'Security events', - isPin: menuAgent.general ? menuAgent.general : false, - }, - fim: { - id: WAZUH_MODULES_ID.INTEGRITY_MONITORING, - text: 'Integrity monitoring', - isPin: menuAgent.fim ? menuAgent.fim : false, - }, - aws: { - id: WAZUH_MODULES_ID.AMAZON_WEB_SERVICES, - text: 'Amazon AWS', - isPin: menuAgent.aws ? menuAgent.aws : false, - }, - gcp: { - id: WAZUH_MODULES_ID.GOOGLE_CLOUD_PLATFORM, - text: 'Google Cloud Platform', - isPin: menuAgent.gcp ? menuAgent.gcp : false, - }, - github: { - id: WAZUH_MODULES_ID.GITHUB, - text: 'GitHub', - isPin: menuAgent.github ? menuAgent.github : false - }, - pm: { - id: WAZUH_MODULES_ID.POLICY_MONITORING, - text: 'Policy Monitoring', - isPin: menuAgent.pm ? menuAgent.pm : false, - }, - sca: { - id: WAZUH_MODULES_ID.SECURITY_CONFIGURATION_ASSESSMENT, - text: 'Security configuration assessment', - isPin: menuAgent.sca ? menuAgent.sca : false, - }, - audit: { - id: WAZUH_MODULES_ID.AUDITING, - text: 'System Auditing', - isPin: menuAgent.audit ? menuAgent.audit : false, - }, - oscap: { - id: WAZUH_MODULES_ID.OPEN_SCAP, - text: 'OpenSCAP', - isPin: menuAgent.oscap ? menuAgent.oscap : false, - }, - ciscat: { - id: WAZUH_MODULES_ID.CIS_CAT, - text: 'CIS-CAT', - isPin: menuAgent.oscap ? menuAgent.oscap : false, - }, - vuls: { - id: WAZUH_MODULES_ID.VULNERABILITIES, - text: 'Vulnerabilities', - isPin: menuAgent.vuls ? menuAgent.vuls : false, - }, - virustotal: { - id: WAZUH_MODULES_ID.VIRUSTOTAL, - text: 'VirusTotal', - isPin: menuAgent.virustotal ? menuAgent.virustotal : false, - }, - osquery: { - id: WAZUH_MODULES_ID.OSQUERY, - text: 'Osquery', - isPin: menuAgent.osquery ? menuAgent.osquery : false, - }, - docker: { - id: WAZUH_MODULES_ID.DOCKER, - text: 'Docker Listener', - isPin: menuAgent.docker ? menuAgent.docker : false, - }, - mitre: { - id: WAZUH_MODULES_ID.MITRE_ATTACK, - text: 'MITRE ATT&CK', - isPin: menuAgent.mitre ? menuAgent.mitre : false, - }, - pci: { - id: WAZUH_MODULES_ID.PCI_DSS, - text: 'PCI DSS', - isPin: menuAgent.pci ? menuAgent.pci : false, - }, - gdpr: { - id: WAZUH_MODULES_ID.GDPR, - text: 'GDPR', - isPin: menuAgent.gdpr ? menuAgent.gdpr : false, - }, - hipaa: { - id: WAZUH_MODULES_ID.HIPAA, - text: 'HIPAA', - isPin: menuAgent.hipaa ? menuAgent.hipaa : false, - }, - nist: { - id: WAZUH_MODULES_ID.NIST_800_53, - text: 'NIST 800-53', - isPin: menuAgent.nist ? menuAgent.nist : false, - }, - tsc: { id: WAZUH_MODULES_ID.TSC, text: 'TSC', isPin: menuAgent.tsc ? menuAgent.tsc : false }, - }; -}; diff --git a/plugins/main/public/components/common/welcome/components/menu-agent.js b/plugins/main/public/components/common/welcome/components/menu-agent.js deleted file mode 100644 index f11013ad54..0000000000 --- a/plugins/main/public/components/common/welcome/components/menu-agent.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Wazuh app - React component for registering agents. - * 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, { Component } from 'react'; -import { - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSideNav, - EuiLink, -} from '@elastic/eui'; -import { connect } from 'react-redux'; -import { hasAgentSupportModule } from '../../../../react-services/wz-agents'; -import { - getAngularModule, - getCore, - getToasts, -} from '../../../../kibana-services'; -import { updateCurrentAgentData } from '../../../../redux/actions/appStateActions'; -import { Applications, Categories } from '../../../../utils/applications'; -import { RedirectAppLinks } from '../../../../../../../src/plugins/opensearch_dashboards_react/public'; - -class WzMenuAgent extends Component { - constructor(props) { - super(props); - this.state = { - hoverAddFilter: '', - }; - - this.appCategories = Applications.reduce((categories, app) => { - const existingCategory = categories.find( - category => category.id === app.category, - ); - if (app.showInAgentMenu) { - if (existingCategory) { - existingCategory.apps.push(app); - } else { - const category = Categories.find( - category => app.category === category.id, - ); - categories.push({ - id: category.id, - label: Categories.find(category => app.category === category.id) - .label, - icon: category.euiIconType, - apps: [app], - }); - } - } - return categories; - }, []).sort((a, b) => { - return ( - Categories.find(category => a.id === category.id).order - - Categories.find(category => b.id === category.id).order - ); - }); - } - - componentDidMount() { - const $injector = getAngularModule().$injector; - this.router = $injector.get('$route'); - } - - clickMenuItem = appId => { - this.props.closePopover(); - // do not redirect if we already are in that tab - this.props.updateCurrentAgentData(this.props.isAgent); - this.router.reload(); - }; - - addToast({ color, title, text, time = 3000 }) { - getToasts().add({ title, text, toastLifeTimeMs: time, color }); - } - - createItems = items => { - return items - .filter(item => - hasAgentSupportModule(this.props.currentAgentData, item.id), - ) - .map(item => this.createItem(item)); - }; - - createItem = (item, data = {}) => { - // NOTE: Duplicate `name` values will cause `id` collisions. - return { - ...data, - id: item.id, - name: ( - { - this.setState({ hoverAddFilter: item.id }); - }} - onMouseLeave={() => { - this.setState({ hoverAddFilter: '' }); - }} - > - (!item.isTitle ? this.clickMenuItem(item.id) : null)} - style={{ cursor: !item.isTitle ? 'pointer' : 'normal' }} - > - - - {item.title} - - - - {this.state.hoverAddFilter === item.id && - !item.isTitle && - (this.props.pinnedApplications.length < 6 || item.isPin) && - (this.props.pinnedApplications.length > 1 || !item.isPin) && ( - - { - if ( - !item.isPin && - this.props.pinnedApplications.length < 6 - ) { - this.props.updatePinnedApplications([ - ...this.props.pinnedApplications, - item.id, - ]); - } else if ( - this.props.pinnedApplications.includes(item.id) - ) { - this.props.updatePinnedApplications([ - ...this.props.pinnedApplications.filter( - id => id !== item.id, - ), - ]); - } else { - this.addToast({ - title: - 'The limit of pinned applications has been reached', - color: 'danger', - }); - } - }} - color='primary' - type={ - this.props.pinnedApplications.includes(item.id) - ? 'pinFilled' - : 'pin' - } - aria-label='Next' - style={{ cursor: 'pointer' }} - /> - - )} - - ), - isSelected: this.props.currentTab === item.id, - }; - }; - - render() { - const items = this.appCategories.map(({ apps, ...rest }) => ({ - ...rest, - items: this.createItems( - apps.map(app => ({ - id: app.id, - title: app.title, - isPin: this.props.pinnedApplications.includes(app.id), - })), - ), - })); - - return ( -
-
- - {items.map(item => ( - - , - items: item.items, - }, - ]} - style={{ padding: '4px 12px' }} - /> - - ))} - -
-
- ); - } -} - -const mapStateToProps = state => { - return { - currentAgentData: state.appStateReducers.currentAgentData, - currentTab: state.appStateReducers.currentTab, - }; -}; - -const mapDispatchToProps = dispatch => ({ - updateCurrentAgentData: agentData => - dispatch(updateCurrentAgentData(agentData)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(WzMenuAgent); diff --git a/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx b/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx index 9814642042..39224fde2c 100644 --- a/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx +++ b/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx @@ -90,6 +90,12 @@ export const ScaScan = compose( this.getLastScan(this.props.agent.id); } + async componentDidUpdate(prevProps: Readonly) { + if (prevProps.agent.id !== this.props.agent.id) { + this.getLastScan(this.props.agent.id); + } + } + async getLastScan(agentId: Number) { const scans = await WzRequest.apiReq( 'GET', @@ -209,7 +215,7 @@ export const ScaScan = compose( href={getCore().application.getUrlForApp( configurationAssessment.id, { - path: `#/overview?tab=sca&redirectPolicy=${lastScan.policy_id}`, + path: `#/overview?tab=sca&redirectPolicy=${lastScan?.policy_id}`, }, )} > @@ -220,7 +226,7 @@ export const ScaScan = compose(
- {lastScan.policy_id} + {lastScan?.policy_id} @@ -258,6 +264,20 @@ export const ScaScan = compose( const loading = this.renderLoadingStatus(); const scaScan = this.renderScanDetails(); const emptyPrompt = this.renderEmptyPrompt(); + if (loading) { + return ( + + {loading} + + ); + } + if (!lastScan) { + return ( + + {emptyPrompt} + + ); + } return ( @@ -277,7 +297,7 @@ export const ScaScan = compose( href={getCore().application.getUrlForApp( configurationAssessment.id, { - path: `#/overview?tab=sca&redirectPolicy=${lastScan.policy_id}`, + path: `#/overview?tab=sca&redirectPolicy=${lastScan?.policy_id}`, }, )} > @@ -309,8 +329,6 @@ export const ScaScan = compose( - {lastScan === undefined && emptyPrompt} - {loading} {scaScan} diff --git a/plugins/main/public/components/common/welcome/welcome.scss b/plugins/main/public/components/common/welcome/welcome.scss index e04bf5525c..dab373eee2 100644 --- a/plugins/main/public/components/common/welcome/welcome.scss +++ b/plugins/main/public/components/common/welcome/welcome.scss @@ -1,44 +1,84 @@ -.wz-welcome-page .euiCard .euiTitle, .wz-module-body .euiCard .euiTitle { - font-size: 16px; - font-weight: 400; +.wz-welcome-page .euiCard .euiTitle, +.wz-module-body .euiCard .euiTitle { + font-size: 16px; + font-weight: 400; } -.wz-welcome-page .euiCard .euiText, .wz-module-body .euiCard .euiText { - font-size: 12px; - font-family: sans-serif; +.wz-welcome-page .euiCard .euiText, +.wz-module-body .euiCard .euiText { + font-size: 12px; + font-family: sans-serif; } -.wz-module-header-agent:not(.wz-module-header-agent-main){ - background: white; - border-bottom: 1px solid #D3DAE6; +.wz-module-header-agent:not(.wz-module-header-agent-main) { + background: white; + border-bottom: 1px solid #d3dae6; } -.wz-welcome-page-agent-info.wz-welcome-page-agent-info-gray{ - border-top: 1px solid #d3dae6; - background: #fafbfd!important; - border-bottom: 1px solid #d3dae6; +.wz-welcome-page-agent-info.wz-welcome-page-agent-info-gray { + border-top: 1px solid #d3dae6; + background: #fafbfd !important; + border-bottom: 1px solid #d3dae6; } -.wz-welcome-page-agent-tabs{ - padding: 12px 16px 1px 10px; - min-height: 54px; - border-bottom: 1px solid #D3DAE6; - background-color: white; +.wz-welcome-page-agent-tabs { + padding: 12px 16px 1px 10px; + min-height: 54px; + border-bottom: 1px solid #d3dae6; + background-color: white; } -.wz-welcome-page-agent-info-actions{ - padding: 6px 0px 6px 0px; +.wz-welcome-page-agent-info-actions { + padding: 6px 0px 6px 0px; } -.wz-welcome-page-agent-info .euiStat .euiText{ - font-size: 12px; - font-family: sans-serif; +.wz-welcome-page-agent-info .euiStat .euiText { + font-size: 12px; + font-family: sans-serif; } .statWithLink:hover .euiTitle { - text-decoration: underline; + text-decoration: underline; } -span.statWithLink:hover { - text-decoration: underline; -} \ No newline at end of file +span.statWithLink:hover { + text-decoration: underline; +} + +// Header buttons of IT Hygiene application + +// Sidebar is open and locked +body.euiBody--hasFlyout:not(.euiBody-hasOverlayMask) { + @media only screen and (max-width: 1345px) { + // Hide button text depending on the window size + .wz-it-hygiene-header-button .euiButtonEmpty__text { + display: none; + } + } + + @media only screen and (min-width: 1346px) { + // Hide the tooltip of button depending on the window size + .wz-it-hygiene-header-button-tooltip { + display: none; + } + } +} + +// Sidebar is closed +body:not(.euiBody--hasFlyout) { + @media only screen and (max-width: 1025px) { + // Hide button text depending on the window size + .wz-it-hygiene-header-button .euiButtonEmpty__text { + display: none; + } + } + + @media only screen and (min-width: 1026px) { + // Hide the tooltip of button depending on the window size + .wz-it-hygiene-header-button-tooltip { + display: none; + } + } +} + +// Header buttons of IT Hygiene application diff --git a/plugins/main/public/components/health-check/container/health-check.container.tsx b/plugins/main/public/components/health-check/container/health-check.container.tsx index 29c1252545..f3be74bc46 100644 --- a/plugins/main/public/components/health-check/container/health-check.container.tsx +++ b/plugins/main/public/components/health-check/container/health-check.container.tsx @@ -37,6 +37,7 @@ import { WAZUH_INDEX_TYPE_MONITORING, WAZUH_INDEX_TYPE_STATISTICS, WAZUH_INDEX_TYPE_VULNERABILITIES, + WAZUH_INDEX_TYPE_FIM, } from '../../../../common/constants'; import { compose } from 'redux'; @@ -103,6 +104,19 @@ const checks = { shouldCheck: false, canRetry: true, }, + 'fim.pattern': { + title: 'Check fim index pattern', + label: 'Fim index pattern', + validator: appConfig => + checkPatternSupportService( + appConfig.data['fim.pattern'], + WAZUH_INDEX_TYPE_FIM, + NOT_TIME_FIELD_NAME_INDEX_PATTERN, + ), + awaitFor: [], + shouldCheck: false, + canRetry: true, + }, }; function HealthCheckComponent() { diff --git a/plugins/main/public/components/health-check/services/check-pattern-support.service.ts b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts index 611f6fd1a2..aca4d986a4 100644 --- a/plugins/main/public/components/health-check/services/check-pattern-support.service.ts +++ b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts @@ -12,6 +12,7 @@ * */ import { SavedObject } from '../../../react-services'; +import { WarningError } from '../../../react-services/error-management/error-factory/errors/WarningError'; import { CheckLogger } from '../types/check_logger'; export const checkPatternSupportService = @@ -29,52 +30,50 @@ export const checkPatternSupportService = `Getting indices fields for the index pattern id [${pattern}]...`, ); const fields = await SavedObject.getIndicesFields(pattern, indexType); - if (fields) { - checkLogger.info( - `Fields for index pattern id [${pattern}] found: ${fields.length}`, - ); - checkLogger.info(`Creating saved object for the index pattern with id [${pattern}]. + checkLogger.info( + `Fields for index pattern id [${pattern}] found: ${fields.length}`, + ); + checkLogger.info(`Creating saved object for the index pattern with id [${pattern}]. title: ${pattern} id: ${pattern} timeFieldName: ${timeFieldName} ${fields ? `fields: ${fields.length}` : ''}`); - await SavedObject.createSavedObject( - 'index-pattern', - pattern, - { - attributes: { - title: pattern, - timeFieldName, - }, + await SavedObject.createSavedObject( + 'index-pattern', + pattern, + { + attributes: { + title: pattern, + timeFieldName, }, - fields, - ); - checkLogger.action( - `Created the saved object for the index pattern id [${pattern}]`, - ); - const indexPatternSavedObjectIDs = [pattern]; - // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint. - // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293 - checkLogger.info( - `Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join( - ',', - )} can be found...`, - ); - await SavedObject.validateIndexPatternSavedObjectCanBeFound( - indexPatternSavedObjectIDs, - ); - checkLogger.info('Integrity of saved objects: [ok]'); + }, + fields, + ); + checkLogger.action( + `Created the saved object for the index pattern id [${pattern}]`, + ); + const indexPatternSavedObjectIDs = [pattern]; + // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint. + // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293 + checkLogger.info( + `Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join( + ',', + )} can be found...`, + ); + await SavedObject.validateIndexPatternSavedObjectCanBeFound( + indexPatternSavedObjectIDs, + ); + checkLogger.info('Integrity of saved objects: [ok]'); + } catch (error) { + if (error.name === 'WarningError') { + checkLogger.warning(error.message || error); } else { - checkLogger.warning( - `No indices fields found for index pattern id [${pattern}], it is necessary to check the index...`, + checkLogger.error( + `Error creating index pattern id [${pattern}]: ${ + error.message || error + }`, ); } - } catch (error) { - checkLogger.error( - `Error creating index pattern id [${pattern}]: ${ - error.message || error - }`, - ); } } }; diff --git a/plugins/main/public/components/overview/fim/dashboard/index.ts b/plugins/main/public/components/overview/fim/dashboard/index.ts new file mode 100644 index 0000000000..ee1046d206 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/index.ts @@ -0,0 +1 @@ +export * from './overview'; diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx new file mode 100644 index 0000000000..09a779564e --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx @@ -0,0 +1,153 @@ +import React from 'react'; +import { getPlugins } from '../../../../../kibana-services'; +import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { getDashboardPanels } from './dashboard_panels'; +import { I18nProvider } from '@osd/i18n/react'; +import useSearchBarConfiguration from '../../../vulnerabilities/search_bar/use_search_bar_configuration'; +import { getDashboardFilters } from './dashboard_panels_filters'; +import './fim_filters.scss'; +import { getKPIsPanel } from './dashboard_panels_kpis'; +import { useAppConfig } from '../../../../common/hooks'; +import { withErrorBoundary } from '../../../../common/hocs'; +import { DiscoverNoResults } from '../../../vulnerabilities/common/components/no_results'; +import { WAZUH_INDEX_TYPE_FIM } from '../../../../../../common/constants'; +import { LoadingSpinner } from '../../../vulnerabilities/common/components/loading_spinner'; +import useCheckIndexFields from '../../../vulnerabilities/common/hooks/useCheckIndexFields'; + +const plugins = getPlugins(); + +const SearchBar = getPlugins().data.ui.SearchBar; + +const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; + +const DashboardFimComponent: React.FC = () => { + console.log('El componente está montándose...'); + + const appConfig = useAppConfig(); + + const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern']; + console.log(FIM_INDEX_PATTERN_ID, 'FIM_INDEX_PATTERN_ID'); + console.log( + FIM_INDEX_PATTERN_ID, + 'FIM_INDEX_PATTERN_ID antes de useSearchBarConfiguration', + ); + + const { searchBarProps } = useSearchBarConfiguration({ + defaultIndexPatternID: FIM_INDEX_PATTERN_ID, + }); + const { + isLoading: isLoadingSearchbar, + filters, + query, + indexPatterns, + } = searchBarProps; + + const { isError, error, isSuccess, resultIndexData, isLoading } = + useCheckIndexFields( + FIM_INDEX_PATTERN_ID, + indexPatterns?.[0], + WAZUH_INDEX_TYPE_FIM, + filters, + query, + ); + console.log(searchBarProps, 'searchBarProps'); + console.log(indexPatterns, 'indexPatterns'); + + return ( + <> + + <> + {isLoading || isLoadingSearchbar ? : null} + {!isLoading && !isLoadingSearchbar ? ( + + ) : null} + {!isLoadingSearchbar && + !isLoading && + (isError || resultIndexData?.hits?.total === 0) ? ( + + ) : null} + {!isLoadingSearchbar && !isLoading && isSuccess ? ( + <> +
+ +
+ + + + ) : null} + +
+ + ); +}; + +export const DashboardFim = withErrorBoundary(DashboardFimComponent); diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts new file mode 100644 index 0000000000..e9fdb21227 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts @@ -0,0 +1,632 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateTopFim = (indexPatternId: string) => { + return { + id: 'most_detected_fim', + title: 'Most detected fim', + type: 'horizontal_bar', + params: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 200, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 75, + filter: true, + 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: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + }, + 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: '2', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'Vulnerability.ID', + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateTopFimEndpoints = (indexPatternId: string) => { + return { + id: 'most_vulnerable_endpoints_fim', + title: 'The most vulnerable endpoints', + type: 'horizontal_bar', + params: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 200, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 75, + filter: true, + 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: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + }, + uiState: { + vis: { + legendOpen: 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', + params: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'agent.id', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'package.path', + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateAccumulationMostDetectedFim = (indexPatternId: string) => { + return { + id: 'accumulation_most_vulnerable_fim', + title: 'Accumulation of the most detected fim', + type: 'line', + params: { + type: 'line', + 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: 'line', + mode: 'normal', + data: { + label: 'Count', + id: '1', + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: false, + lineWidth: 2, + interpolate: 'linear', + showCircles: true, + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + radiusRatio: 20, + }, + 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: '2', + enabled: true, + type: 'count', + params: {}, + schema: 'radius', + }, + { + id: '4', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Others', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + { + id: '3', + enabled: true, + type: 'date_histogram', + params: { + field: '@timestamp', + timeRange: { + from: 'now-24h', + to: 'now', + }, + useNormalizedOpenSearchInterval: true, + scaleMetricValues: false, + interval: 'w', + // eslint-disable-next-line camelcase + drop_partials: false, + // eslint-disable-next-line camelcase + min_doc_count: 1, + // eslint-disable-next-line camelcase + extended_bounds: {}, + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateInventoryTable = (indexPatternId: string) => { + return { + id: 'inventory_table_fim', + title: 'Inventory table', + type: 'table', + params: { + perPage: 5, + showPartialRows: false, + showMetricsAtAllLevels: false, + showTotal: false, + totalFunc: 'sum', + percentageCol: '', + }, + 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: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'package.name', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'name', + }, + schema: 'bucket', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'package.version', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'version', + }, + schema: 'bucket', + }, + { + id: '5', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.severity', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'severity', + }, + schema: 'bucket', + }, + { + id: '6', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'id', + }, + schema: 'bucket', + }, + { + id: '7', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.score.version', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'score version', + }, + schema: 'bucket', + }, + { + id: '8', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.score.base', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'score base', + }, + schema: 'bucket', + }, + ], + }, + }; +}; + +export const getDashboardPanels = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '6': { + gridData: { + w: 16, + h: 12, + x: 0, + y: 0, + i: '6', + }, + type: 'visualization', + explicitInput: { + id: '6', + savedVis: getVisStateTopFim(indexPatternId), + }, + }, + '7': { + gridData: { + w: 16, + h: 12, + x: 16, + y: 0, + i: '7', + }, + type: 'visualization', + explicitInput: { + id: '7', + savedVis: getVisStateTopFimEndpoints(indexPatternId), + }, + }, + '8': { + gridData: { + w: 16, + h: 12, + x: 32, + y: 0, + i: '8', + }, + type: 'visualization', + explicitInput: { + id: '8', + savedVis: getVisStateAccumulationMostDetectedFim(indexPatternId), + }, + }, + '9': { + gridData: { + w: 48, + h: 12, + x: 0, + y: 14, + i: '9', + }, + type: 'visualization', + explicitInput: { + id: '9', + savedVis: getVisStateInventoryTable(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_filters.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_filters.ts new file mode 100644 index 0000000000..50e85411b4 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_filters.ts @@ -0,0 +1,160 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateFilter = ( + id: string, + indexPatternId: string, + title: string, + label: string, + fieldName: string, +) => { + return { + id, + title, + type: 'table', + params: { + perPage: 5, + percentageCol: '', + row: true, + showMetricsAtAllLevels: false, + showPartialRows: false, + showTotal: false, + totalFunc: 'sum', + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: fieldName, + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: label, + }, + schema: 'bucket', + }, + ], + }, + }; +}; + +export const getDashboardFilters = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + topPackageSelector: { + gridData: { + w: 12, + h: 12, + x: 0, + y: 0, + i: 'topPackageSelector', + }, + type: 'visualization', + explicitInput: { + id: 'topPackageSelector', + savedVis: getVisStateFilter( + 'topPackageSelector', + indexPatternId, + 'Top Packages fim', + 'Package', + 'package.name', + ), + }, + }, + topOSFim: { + gridData: { + w: 12, + h: 12, + x: 12, + y: 0, + i: 'topOSFim', + }, + type: 'visualization', + explicitInput: { + id: 'topOSFim', + savedVis: getVisStateFilter( + 'topOSFim', + indexPatternId, + 'Top Operating system fim', + 'Operating system', + 'host.os.full', + ), + }, + }, + topAgentFim: { + gridData: { + w: 12, + h: 12, + x: 24, + y: 0, + i: 'topAgentFim', + }, + type: 'visualization', + explicitInput: { + id: 'topAgentFim', + savedVis: getVisStateFilter( + 'topAgentFim', + indexPatternId, + 'Agent filter', + 'Agent', + 'agent.id', + ), + }, + }, + topFim: { + gridData: { + w: 12, + h: 12, + x: 36, + y: 0, + i: 'topFim', + }, + type: 'visualization', + explicitInput: { + id: 'topFim', + savedVis: getVisStateFilter( + 'topFim', + indexPatternId, + 'Top fim', + 'Vulnerability', + 'vulnerability.id', + ), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts new file mode 100644 index 0000000000..f36425473a --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts @@ -0,0 +1,414 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateSeverityCritical = (indexPatternId: string) => { + return { + id: 'severity_critical_fim', + title: 'Critical', + 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: 50, + }, + }, + }, + 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: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'data.vulnerability.severity: "Critical"', + language: 'kuery', + }, + label: '', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityHigh = (indexPatternId: string) => { + return { + id: 'severity_high_fim', + title: 'High', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Blues', + 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: 50, + }, + }, + }, + uiState: { + vis: { + colors: { + 'High Severity Alerts - Count': '#38D1BA', + }, + }, + }, + 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: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"High"', + language: 'kuery', + }, + label: '- High Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityMedium = (indexPatternId: string) => { + return { + id: 'severity_medium_fim', + title: 'Medium', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Yellow to Red', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: true, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 50, + }, + }, + }, + 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: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"Medium"', + language: 'kuery', + }, + label: '- Medium Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityLow = (indexPatternId: string) => { + return { + id: 'severity_low_fim', + title: 'Low', + 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: 50, + }, + }, + }, + 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: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"Low"', + language: 'kuery', + }, + label: '- Low Severity Alerts', + }, + ], + }, + 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: getVisStateSeverityCritical(indexPatternId), + }, + }, + '2': { + gridData: { + w: 12, + h: 6, + x: 12, + y: 0, + i: '2', + }, + type: 'visualization', + explicitInput: { + id: '2', + savedVis: getVisStateSeverityHigh(indexPatternId), + }, + }, + '3': { + gridData: { + w: 12, + h: 6, + x: 24, + y: 0, + i: '3', + }, + type: 'visualization', + explicitInput: { + id: '3', + savedVis: getVisStateSeverityMedium(indexPatternId), + }, + }, + '4': { + gridData: { + w: 12, + h: 6, + x: 36, + y: 0, + i: '4', + }, + type: 'visualization', + explicitInput: { + id: '4', + savedVis: getVisStateSeverityLow(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/fim_filters.scss b/plugins/main/public/components/overview/fim/dashboard/overview/fim_filters.scss new file mode 100644 index 0000000000..a836b86e3e --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/fim_filters.scss @@ -0,0 +1,6 @@ +.fim-dashboard-filters-wrapper { + .euiDataGrid__controls, + .euiDataGrid__pagination { + display: none !important; + } +} diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/index.tsx b/plugins/main/public/components/overview/fim/dashboard/overview/index.tsx new file mode 100644 index 0000000000..b691822976 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/overview/index.tsx @@ -0,0 +1 @@ +export * from './dashboard'; \ No newline at end of file diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.scss b/plugins/main/public/components/overview/fim/inventory/inventory.scss new file mode 100644 index 0000000000..2303093ed2 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/inventory.scss @@ -0,0 +1,13 @@ +.fimInventoryContainer { + height: calc(100vh - 104px); +} + +.headerIsExpanded .fimInventoryContainer { + height: calc(100vh - 153px); +} + +.fimInventoryContainer .euiDataGrid--fullScreen { + height: calc(100vh - 49px); + bottom: 0; + top: auto; +} diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.tsx b/plugins/main/public/components/overview/fim/inventory/inventory.tsx new file mode 100644 index 0000000000..c1ecc93fe1 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/inventory.tsx @@ -0,0 +1,265 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { getPlugins } from '../../../../kibana-services'; +import useSearchBarConfiguration from '../../fim/search_bar/use_search_bar_configuration'; + +import { IntlProvider } from 'react-intl'; +import { + EuiDataGrid, + EuiPageTemplate, + EuiToolTip, + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, +} from '@elastic/eui'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { SearchResponse } from '../../../../../../../../src/core/server'; +import DocViewer from '../../fim/doc_viewer/doc_viewer'; +import { DiscoverNoResults } from '../../fim/common/components/no_results'; +import { LoadingSpinner } from '../../fim/common/components/loading_spinner'; +import { useDataGrid } from '../../fim/data_grid/use_data_grid'; +import { + MAX_ENTRIES_PER_QUERY, + inventoryTableDefaultColumns, +} from '../../fim/dashboards/inventory/config'; +import { useDocViewer } from '../../fim/doc_viewer/use_doc_viewer'; +import './inventory.scss'; +import { + search, + exportSearchToCSV, +} from '../../fim/dashboards/inventory/inventory_service'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../react-services/error-management'; +import { withErrorBoundary } from '../../../common/hocs'; +import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; +import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers'; +import { useAppConfig } from '../../../common/hooks'; +import { WAZUH_INDEX_TYPE_FIM } from '../../../../../common/constants'; +import useCheckIndexFields from '../../fim/common/hooks/useCheckIndexFields'; + +const InventoryFimComponent = () => { + const appConfig = useAppConfig(); + const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern']; + const { searchBarProps } = useSearchBarConfiguration({ + defaultIndexPatternID: FIM_INDEX_PATTERN_ID, + }); + const { isLoading, filters, query, indexPatterns } = searchBarProps; + const SearchBar = getPlugins().data.ui.SearchBar; + const [results, setResults] = useState({} as SearchResponse); + const [inspectedHit, setInspectedHit] = useState(undefined); + const [indexPattern, setIndexPattern] = useState( + undefined, + ); + const [isSearching, setIsSearching] = useState(false); + const [isExporting, setIsExporting] = useState(false); + + 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: 'Fim Inventory Table', + defaultColumns: inventoryTableDefaultColumns, + results, + indexPattern: indexPattern as IndexPattern, + DocViewInspectButton, + }); + + const { pagination, sorting, columnVisibility } = dataGridProps; + + const docViewerProps = useDocViewer({ + doc: inspectedHit, + indexPattern: indexPattern as IndexPattern, + }); + + const { + isError, + error, + isSuccess, + resultIndexData, + isLoading: isLoadingCheckIndex, + } = useCheckIndexFields( + FIM_INDEX_PATTERN_ID, + indexPatterns?.[0], + WAZUH_INDEX_TYPE_FIM, + filters, + query, + ); + + useEffect(() => { + if (!isLoading && isSuccess) { + setIndexPattern(indexPatterns?.[0] as IndexPattern); + search({ + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + query, + pagination, + sorting, + }) + .then(results => { + setResults(results); + setIsSearching(false); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching fim', + }); + ErrorHandler.handleError(searchError); + setIsSearching(false); + }); + } + }, [ + JSON.stringify(searchBarProps), + JSON.stringify(pagination), + JSON.stringify(sorting), + isLoadingCheckIndex, + ]); + + const onClickExportResults = async () => { + const params = { + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + 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 ( + + + <> + {isLoading || isLoadingCheckIndex ? ( + + ) : ( + + )} + {isSearching ? : null} + {!isLoading && + !isSearching && + (isError || + results?.hits?.total === 0 || + resultIndexData?.hits?.total === 0) ? ( + + ) : null} + {!isLoading && + !isSearching && + isSuccess && + 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 + + + ), + }} + /> + ) : null} + {inspectedHit && ( + setInspectedHit(undefined)} size='m'> + + +

Document Details

+
+
+ + + + + + + +
+ )} + +
+
+ ); +}; + +export const InventoryFim = withErrorBoundary(InventoryFimComponent); diff --git a/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx b/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx index 3d592c867d..babfd51d32 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx @@ -28,170 +28,32 @@ * under the License. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; -import { - EuiCallOut, - EuiCode, - EuiDescriptionList, - EuiLink, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiCallOut, EuiPanel } from '@elastic/eui'; interface Props { - timeFieldName?: string; - queryLanguage?: string; + message?: string; } -export const DiscoverNoResults = ({ timeFieldName, queryLanguage }: Props) => { - let timeFieldMessage; - - if (timeFieldName) { - timeFieldMessage = ( - - - - -

- -

- -

- -

-
-
- ); - } - - let luceneQueryMessage; - - if (queryLanguage === 'lucene') { - const searchExamples = [ - { - description: 200, - title: ( - - - - - - ), - }, - { - description: status:200, - title: ( - - - - - - ), - }, - { - description: status:[400 TO 499], - title: ( - - - - - - ), - }, - { - description: status:[400 TO 499] AND extension:PHP, - title: ( - - - - - - ), - }, - { - description: status:[400 TO 499] AND (extension:php OR extension:html), - title: ( - - - - - - ), - }, - ]; - - luceneQueryMessage = ( - - - - -

- -

- -

- -

-
- - - - - - -
- ); - } - +export const DiscoverNoResults = ({ message }: Props) => { return ( - + + message ?? ( + + ) } - color="warning" - iconType="help" - data-test-subj="discoverNoResults" + color='warning' + iconType='help' + data-test-subj='discoverNoResults' /> - {timeFieldMessage} - {luceneQueryMessage} ); diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx new file mode 100644 index 0000000000..75112cbc85 --- /dev/null +++ b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx @@ -0,0 +1,83 @@ +import { useState, useEffect } from 'react'; +import { search } from '../../dashboards/inventory/inventory_service'; +import { + IIndexPattern, + IndexPattern, + Filter, +} from '../../../../../../../../src/plugins/data/public'; +import { + ErrorFactory, + HttpError, +} from '../../../../../react-services/error-management'; +import { SavedObject } from '../../../../../react-services'; + +interface UseCheckIndexFieldsResult { + isLoading: boolean; + isSuccess: boolean; + isError: boolean; + error: Error | null; + resultIndexData: any; +} + +const useCheckIndexFields = ( + indexPatternId: string, + indexPattern: IIndexPattern | undefined, + indexType: string, + filters?: Filter[], + query?: any, +) => { + const [isError, setIsError] = useState(false); + const [error, setError] = useState(null); + const [isSuccess, setIsSuccess] = useState(false); + const [resultIndexData, setResultIndexData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (indexPatternId) { + const checkIndexFields = async () => { + try { + // Check that the index exists + await SavedObject.getIndicesFields(indexPatternId, indexType); + setIsSuccess(true); + + // Check that the index has data + search({ + indexPattern: indexPattern as IndexPattern, + filters, + query, + }) + .then((results: any) => { + setResultIndexData(results); + setIsLoading(false); + }) + .catch((error: any) => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + setError(searchError); + setIsError(true); + setIsLoading(false); + }); + } catch (error) { + setError(error); + setIsError(true); + setIsSuccess(false); + setIsLoading(false); + } + }; + + checkIndexFields(); + } + }, [indexPatternId]); + + return { + isError, + error, + isSuccess, + resultIndexData, + isLoading, + } as UseCheckIndexFieldsResult; +}; + +export default useCheckIndexFields; diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts index f6d6c14a0d..c6e98c1bb0 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts @@ -9,9 +9,6 @@ export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [ { id: 'package.version', }, - { - id: 'package.architecture', - }, { id: 'vulnerability.severity', }, @@ -24,7 +21,4 @@ export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [ { id: 'vulnerability.score.base', }, - { - id: 'event.created', - } ] \ No newline at end of file diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index 4f7cdc1518..121b356289 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -15,8 +15,6 @@ import { EuiFlyoutHeader, EuiTitle, EuiButtonEmpty, - EuiCallOut, - EuiSpacer, } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../../../src/core/server'; @@ -37,6 +35,8 @@ import { withErrorBoundary } from '../../../../common/hocs'; import { HitsCounter } from '../../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; import { formatNumWithCommas } from '../../../../../kibana-integrations/discover/application/helpers'; import { useAppConfig } from '../../../../common/hooks'; +import { WAZUH_INDEX_TYPE_VULNERABILITIES } from '../../../../../../common/constants'; +import useCheckIndexFields from '../../common/hooks/useCheckIndexFields'; const InventoryVulsComponent = () => { const appConfig = useAppConfig(); @@ -93,8 +93,22 @@ const InventoryVulsComponent = () => { indexPattern: indexPattern as IndexPattern, }); + const { + isError, + error, + isSuccess, + resultIndexData, + isLoading: isLoadingCheckIndex, + } = useCheckIndexFields( + VULNERABILITIES_INDEX_PATTERN_ID, + indexPatterns?.[0], + WAZUH_INDEX_TYPE_VULNERABILITIES, + filters, + query, + ); + useEffect(() => { - if (!isLoading) { + if (!isLoading && isSuccess) { setIndexPattern(indexPatterns?.[0] as IndexPattern); search({ indexPattern: indexPatterns?.[0] as IndexPattern, @@ -120,12 +134,9 @@ const InventoryVulsComponent = () => { JSON.stringify(searchBarProps), JSON.stringify(pagination), JSON.stringify(sorting), + isLoadingCheckIndex, ]); - const timeField = indexPattern?.timeFieldName - ? indexPattern.timeFieldName - : undefined; - const onClickExportResults = async () => { const params = { indexPattern: indexPatterns?.[0] as IndexPattern, @@ -161,7 +172,7 @@ const InventoryVulsComponent = () => { grow > <> - {isLoading ? ( + {isLoading || isLoadingCheckIndex ? ( ) : ( { /> )} {isSearching ? : null} - {!isLoading && !isSearching && results?.hits?.total === 0 ? ( - + {!isLoading && + !isSearching && + (isError || + results?.hits?.total === 0 || + resultIndexData?.hits?.total === 0) ? ( + ) : null} - {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + {!isLoading && + !isSearching && + isSuccess && + results?.hits?.total > 0 ? ( } -export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { +export const getFieldValueFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { const field = indexPattern.fields.find((field) => field.name === columnId); let fieldValue = null; if (columnId.includes('.')) { @@ -93,13 +93,19 @@ export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) fieldValue = fieldValue[field]; } }); - } else { - fieldValue = rowsParsed[rowIndex][columnId].formatted - ? rowsParsed[rowIndex][columnId].formatted - : rowsParsed[rowIndex][columnId]; + const rowValue = rowsParsed[rowIndex]; + // when not exist the column in the row value then the value is null + if(!rowValue.hasOwnProperty(columnId)){ + fieldValue = null; + }else{ + fieldValue = rowValue[columnId]?.formatted || rowValue[columnId]; + } + } + // when fieldValue is null or undefined then return a empty string + if (fieldValue === null || fieldValue === undefined) { + return ''; } - // if is date field if (field?.type === 'date') { // @ts-ignore diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx index 5328214a06..911548c996 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -8,6 +8,11 @@ import { getDashboardFilters } from './dashboard_panels_filters'; import './vulnerability_detector_filters.scss'; import { getKPIsPanel } from './dashboard_panels_kpis'; import { useAppConfig } from '../../../../common/hooks'; +import { withErrorBoundary } from '../../../../common/hocs'; +import { DiscoverNoResults } from '../../common/components/no_results'; +import { WAZUH_INDEX_TYPE_VULNERABILITIES } from '../../../../../../common/constants'; +import { LoadingSpinner } from '../../common/components/loading_spinner'; +import useCheckIndexFields from '../../common/hooks/useCheckIndexFields'; const plugins = getPlugins(); const SearchBar = getPlugins().data.ui.SearchBar; @@ -18,95 +23,127 @@ const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; a wrapper for visual adjustments, while the Kpi, the Open vs Close visualization and the rest of the visualizations have different configurations at the dashboard level. */ -export const DashboardVuls: React.FC = () => { +const DashboardVulsComponent: React.FC = () => { const appConfig = useAppConfig(); const VULNERABILITIES_INDEX_PATTERN_ID = appConfig.data['vulnerabilities.pattern']; - const { searchBarProps } = useSearchBarConfiguration({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - filters: [], }); + const { + isLoading: isLoadingSearchbar, + filters, + query, + indexPatterns, + } = searchBarProps; + + const { isError, error, isSuccess, resultIndexData, isLoading } = + useCheckIndexFields( + VULNERABILITIES_INDEX_PATTERN_ID, + indexPatterns?.[0], + WAZUH_INDEX_TYPE_VULNERABILITIES, + filters, + query, + ); return ( <> - + <> + {isLoading || isLoadingSearchbar ? : null} + {!isLoading && !isLoadingSearchbar ? ( + + ) : null} + {!isLoadingSearchbar && + !isLoading && + (isError || resultIndexData?.hits?.total === 0) ? ( + + ) : null} + {!isLoadingSearchbar && !isLoading && isSuccess ? ( + <> +
+ +
+ + + + ) : null} +
-
- -
- - ); }; + +export const DashboardVuls = withErrorBoundary(DashboardVulsComponent); diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts index 76aa50fa3b..d4d44a3f23 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts @@ -123,22 +123,6 @@ const getVisStateTopVulnerabilities = (indexPatternId: string) => { }, schema: 'segment', }, - { - id: '3', - enabled: true, - type: 'terms', - params: { - field: 'vulnerability.id', - orderBy: '1', - order: 'desc', - size: 5, - otherBucket: false, - otherBucketLabel: 'Other', - missingBucket: false, - missingBucketLabel: 'Missing', - }, - schema: 'group', - }, ], }, }; @@ -273,23 +257,6 @@ const getVisStateTopVulnerabilitiesEndpoints = (indexPatternId: string) => { }, schema: 'segment', }, - { - id: '3', - enabled: true, - type: 'terms', - params: { - field: 'agent.id', - orderBy: '1', - order: 'desc', - size: 5, - otherBucket: false, - otherBucketLabel: 'Other', - missingBucket: false, - missingBucketLabel: 'Missing', - customLabel: 'mm', - }, - schema: 'group', - }, ], }, }; @@ -431,7 +398,7 @@ const getVisStateAccumulationMostDetectedVulnerabilities = ( enabled: true, type: 'date_histogram', params: { - field: 'event.created', + field: '@timestamp', timeRange: { from: 'now-24h', to: 'now', @@ -526,23 +493,6 @@ const getVisStateInventoryTable = (indexPatternId: string) => { }, schema: 'bucket', }, - { - id: '4', - enabled: true, - type: 'terms', - params: { - field: 'package.architecture', - orderBy: '1', - order: 'desc', - size: 5, - otherBucket: false, - otherBucketLabel: 'Other', - missingBucket: false, - missingBucketLabel: 'Missing', - customLabel: 'architecture', - }, - schema: 'bucket', - }, { id: '5', enabled: true, diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts index 387d5d8f4f..791b505bb1 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts @@ -112,7 +112,7 @@ export const getDashboardFilters = ( indexPatternId, 'Top Operating system vulnerabilities', 'Operating system', - 'host.os.name', + 'host.os.full', ), }, }, diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts index e86749e9ec..a4494d9c05 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts @@ -72,7 +72,7 @@ const getVisStateSeverityCritical = (indexPatternId: string) => { filters: [ { input: { - query: 'vulnerability.severity:"critical"', + query: 'vulnerability.severity:"Critical"', language: 'kuery', }, label: '- Critical Severity Alerts', @@ -164,7 +164,7 @@ const getVisStateSeverityHigh = (indexPatternId: string) => { filters: [ { input: { - query: 'vulnerability.severity:"high"', + query: 'vulnerability.severity:"High"', language: 'kuery', }, label: '- High Severity Alerts', @@ -249,7 +249,7 @@ const getVisStateSeverityMedium = (indexPatternId: string) => { filters: [ { input: { - query: 'vulnerability.severity:"medium"', + query: 'vulnerability.severity:"Medium"', language: 'kuery', }, label: '- Medium Severity Alerts', @@ -334,7 +334,7 @@ const getVisStateSeverityLow = (indexPatternId: string) => { filters: [ { input: { - query: 'vulnerability.severity:"low"', + query: 'vulnerability.severity:"Low"', language: 'kuery', }, label: '- Low Severity Alerts', diff --git a/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts b/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts index a43810fdf9..86dacd8b5d 100644 --- a/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts +++ b/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts @@ -2,7 +2,7 @@ import { EuiDataGridCellValueElementProps, EuiDataGridColumn, EuiDataGridProps, import { useEffect, useMemo, useState, Fragment } from "react"; import { SearchResponse } from "@opensearch-project/opensearch/api/types"; import { IFieldType, IndexPattern } from "../../../../../../../src/plugins/data/common"; -import { parseData, getFieldFormatted } from '../dashboards/inventory/inventory_service'; +import { parseData, getFieldValueFormatted } from '../dashboards/inventory/inventory_service'; import { MAX_ENTRIES_PER_QUERY } from "../dashboards/inventory/config"; type tDataGridProps = { @@ -72,7 +72,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { // then the rowIndex is relative to the current page const relativeRowIndex = rowIndex % pagination.pageSize; return rowsParsed.hasOwnProperty(relativeRowIndex) - ? getFieldFormatted(relativeRowIndex, columnId, indexPattern, rowsParsed) + ? getFieldValueFormatted(relativeRowIndex, columnId, indexPattern, rowsParsed) : null; }; diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts index 37685811d8..8ec10959b9 100644 --- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts +++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts @@ -1,7 +1,10 @@ import { renderHook } from '@testing-library/react-hooks'; import '@testing-library/jest-dom/extend-expect'; // osd dependencies -import { Start, dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { + Start, + dataPluginMock, +} from '../../../../../../../src/plugins/data/public/mocks'; import { Filter, IndexPattern, @@ -41,7 +44,7 @@ mockedGetDataPlugin.mockImplementation( }, }, }, - } as Start) + } as Start), ); /////////////////////////////////////////////////////////// @@ -77,7 +80,9 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.indexPatterns, 'getDefault') .mockResolvedValue(mockedDefaultIndexPatternData); - jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + jest + .spyOn(mockDataPlugin.query.filterManager, 'getFilters') + .mockReturnValue([]); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({})); await waitForNextUpdate(); expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); @@ -93,15 +98,21 @@ describe('[hook] useSearchBarConfiguration', () => { id: exampleIndexPatternId, title: '', }; - jest.spyOn(mockDataPlugin.indexPatterns, 'get').mockResolvedValue(mockedIndexPatternData); + jest + .spyOn(mockDataPlugin.indexPatterns, 'get') + .mockResolvedValue(mockedIndexPatternData); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ defaultIndexPatternID: 'wazuh-index-pattern', - }) + }), ); await waitForNextUpdate(); - expect(mockDataPlugin.indexPatterns.get).toBeCalledWith(exampleIndexPatternId); - expect(result.current.searchBarProps.indexPatterns).toMatchObject([mockedIndexPatternData]); + expect(mockDataPlugin.indexPatterns.get).toBeCalledWith( + exampleIndexPatternId, + ); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + mockedIndexPatternData, + ]); }); it('should show an ERROR message and get the default app index pattern when not found the index pattern data by the ID received', async () => { @@ -112,19 +123,25 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.indexPatterns, 'getDefault') .mockResolvedValue(mockedDefaultIndexPatternData); - jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + jest + .spyOn(mockDataPlugin.query.filterManager, 'getFilters') + .mockReturnValue([]); // mocking console error to avoid logs in test and check if is called - const mockedConsoleError = jest.spyOn(console, 'error').mockImplementationOnce(() => {}); + const mockedConsoleError = jest + .spyOn(console, 'error') + .mockImplementationOnce(() => {}); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ defaultIndexPatternID: 'invalid-index-pattern-id', - }) + }), ); await waitForNextUpdate(); expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); - expect(mockDataPlugin.indexPatterns.get).toBeCalledWith('invalid-index-pattern-id'); + expect(mockDataPlugin.indexPatterns.get).toBeCalledWith( + 'invalid-index-pattern-id', + ); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ mockedDefaultIndexPatternData, ]); @@ -145,50 +162,22 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.indexPatterns, 'getDefault') .mockResolvedValue(mockedDefaultIndexPatternData); - jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue(defaultFilters); + jest + .spyOn(mockDataPlugin.query.filterManager, 'getFilters') + .mockReturnValue(defaultFilters); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ filters: defaultFilters, - }) + }), ); await waitForNextUpdate(); expect(result.current.searchBarProps.filters).toMatchObject(defaultFilters); - expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith(defaultFilters); - expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled(); - }); - - it('should return and preserve filters when the index pattern received is equal to the index pattern already selected in the app', async () => { - const defaultIndexFilters: Filter[] = [ - { - query: 'something to filter', - meta: { - alias: 'filter-mocked', - disabled: false, - negate: true, - }, - }, - ]; - jest - .spyOn(mockDataPlugin.indexPatterns, 'getDefault') - .mockResolvedValue(mockedDefaultIndexPatternData); - jest - .spyOn(mockDataPlugin.indexPatterns, 'get') - .mockResolvedValue(mockedDefaultIndexPatternData); - jest - .spyOn(mockDataPlugin.query.filterManager, 'getFilters') - .mockReturnValue(defaultIndexFilters); - const { result, waitForNextUpdate } = renderHook(() => - useSearchBar({ - defaultIndexPatternID: mockedDefaultIndexPatternData.id, - }) + expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith( + defaultFilters, ); - await waitForNextUpdate(); - expect(result.current.searchBarProps.indexPatterns).toMatchObject([ - mockedDefaultIndexPatternData, - ]); - expect(result.current.searchBarProps.filters).toMatchObject(defaultIndexFilters); + expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled(); }); it('should return empty filters when the index pattern is NOT equal to the default app index pattern', async () => { @@ -204,11 +193,13 @@ describe('[hook] useSearchBarConfiguration', () => { jest .spyOn(mockDataPlugin.indexPatterns, 'getDefault') .mockResolvedValue(mockedDefaultIndexPatternData); - jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + jest + .spyOn(mockDataPlugin.query.filterManager, 'getFilters') + .mockReturnValue([]); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ defaultIndexPatternID: exampleIndexPatternId, - }) + }), ); await waitForNextUpdate(); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx index 377f24930c..4b212f0ae4 100644 --- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { SearchBarProps, FilterManager, @@ -44,6 +44,7 @@ const useSearchBarConfiguration = ( props?: tUseSearchBarProps, ): tUserSearchBarResponse => { // dependencies + const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters'; const filterManager = useFilterManager().filterManager as FilterManager; const { filters } = useFilterManager(); const [query, setQuery] = props?.query @@ -56,29 +57,40 @@ const useSearchBarConfiguration = ( useState(); useEffect(() => { + if (filters && filters.length > 0) { + sessionStorage.setItem( + SESSION_STORAGE_FILTERS_NAME, + JSON.stringify(filters), + ); + } initSearchBar(); + /** + * When the component is disassembled, the original filters that arrived + * when the component was assembled are added. + */ + return () => { + const storagePreviousFilters = sessionStorage.getItem( + SESSION_STORAGE_FILTERS_NAME, + ); + if (storagePreviousFilters) { + const previousFilters = JSON.parse(storagePreviousFilters); + const cleanedFilters = cleanFilters(previousFilters); + filterManager.setFilters(cleanedFilters); + } + }; }, []); - useEffect(() => { - const defaultIndex = props?.defaultIndexPatternID; - /* Filters that do not belong to the default index are filtered */ - const cleanedFilters = filters.filter( - filter => filter.meta.index === defaultIndex, - ); - if (cleanedFilters.length !== filters.length) { - filterManager.setFilters(cleanedFilters); - } - }, [filters]); - /** * Initialize the searchbar props with the corresponding index pattern and filters */ const initSearchBar = async () => { setIsLoading(true); const indexPattern = await getIndexPattern(props?.defaultIndexPatternID); + console.log(props?.defaultIndexPatternID, 'props?.defaultIndexPatternID'); setIndexPatternSelected(indexPattern); - const filters = await getInitialFilters(indexPattern); - filterManager.setFilters(filters); + console.log(indexPattern, 'indexPattern'); + const initialFilters = props?.filters ?? filters; + filterManager.setFilters(initialFilters); setIsLoading(false); }; @@ -87,10 +99,16 @@ const useSearchBarConfiguration = ( * If not receive a ID return the default index from the index pattern service * @returns */ + console.log( + props?.defaultIndexPatternID, + 'props?.defaultIndexPatternID antes de getIndexPattern', + ); + const getIndexPattern = async (indexPatternID?: string) => { const indexPatternService = getDataPlugin() .indexPatterns as IndexPatternsContract; if (indexPatternID) { + console.log(indexPatternID, 'indexPatternID'); try { return await indexPatternService.get(indexPatternID); } catch (error) { @@ -104,44 +122,33 @@ const useSearchBarConfiguration = ( }; /** - * Return the initial filters considering if hook receives initial filters - * When the default index pattern is the same like the received preserve the filters - * @param indexPattern + * Return filters from filters manager. + * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar + * and filters those filters that are not related to the default index pattern * @returns */ - const getInitialFilters = async (indexPattern: IIndexPattern) => { - const indexPatternService = getDataPlugin() - .indexPatterns as IndexPatternsContract; - let initialFilters: Filter[] = []; - if (props?.filters) { - return props?.filters; - } - if (indexPattern) { - // get filtermanager and filters - // if the index is the same, get filters stored - // else clear filters - const defaultIndexPattern = - (await indexPatternService.getDefault()) as IIndexPattern; - initialFilters = - defaultIndexPattern.id === indexPattern.id - ? filterManager.getFilters() - : []; - } else { - initialFilters = []; - } - return initialFilters; + const getFilters = () => { + const originalFilters = filterManager ? filterManager.getFilters() : []; + return originalFilters.filter( + (filter: Filter) => + filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters + filter?.meta?.index === props?.defaultIndexPatternID, + ); }; /** - * Return filters from filters manager. - * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar + * Return cleaned filters. + * Clean the known issue with the auto loaded agent.id filters from the searchbar + * and filters those filters that are not related to the default index pattern + * @param previousFilters * @returns */ - const getFilters = () => { - const filters = filterManager ? filterManager.getFilters() : []; - return filters.filter( - filter => filter.meta.controlledBy !== AUTHORIZED_AGENTS, - ); // remove auto loaded agent.id filters + const cleanFilters = (previousFilters: Filter[]) => { + return previousFilters.filter( + (filter: Filter) => + filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && + filter?.meta?.index !== props?.defaultIndexPatternID, + ); }; /** @@ -156,9 +163,24 @@ const useSearchBarConfiguration = ( dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (filters: Filter[]) => { - // its necessary execute setter to apply filters - filterManager.setFilters(filters); - props?.onFiltersUpdated && props?.onFiltersUpdated(filters); + const storagePreviousFilters = sessionStorage.getItem( + SESSION_STORAGE_FILTERS_NAME, + ); + /** + * If there are persisted filters, it is necessary to add them when + * updating the filters in the filterManager + */ + if (storagePreviousFilters) { + const previousFilters = JSON.parse(storagePreviousFilters); + const cleanedFilters = cleanFilters(previousFilters); + filterManager.setFilters([...cleanedFilters, ...filters]); + + props?.onFiltersUpdated && + props?.onFiltersUpdated([...cleanedFilters, ...filters]); + } else { + filterManager.setFilters(filters); + props?.onFiltersUpdated && props?.onFiltersUpdated(filters); + } }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, diff --git a/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap b/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap index f1bad4e5d4..8d16ea7342 100644 --- a/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap +++ b/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap @@ -17,46 +17,6 @@ exports[`SearchBar component Renders correctly to match the snapshot of query la
-
-
- -
-
diff --git a/plugins/main/public/components/search-bar/query-language/wql.test.tsx b/plugins/main/public/components/search-bar/query-language/wql.test.tsx index 2d2a1b3171..a803a79ecf 100644 --- a/plugins/main/public/components/search-bar/query-language/wql.test.tsx +++ b/plugins/main/public/components/search-bar/query-language/wql.test.tsx @@ -41,10 +41,6 @@ describe('SearchBar component', () => { const wrapper = render(); await waitFor(() => { - const elementImplicitQuery = wrapper.container.querySelector( - '.euiCodeBlock__code', - ); - expect(elementImplicitQuery?.innerHTML).toEqual('id!=000 and '); expect(wrapper.container).toMatchSnapshot(); }); }); diff --git a/plugins/main/public/components/search-bar/query-language/wql.tsx b/plugins/main/public/components/search-bar/query-language/wql.tsx index 191f70b2d9..5282a6e096 100644 --- a/plugins/main/public/components/search-bar/query-language/wql.tsx +++ b/plugins/main/public/components/search-bar/query-language/wql.tsx @@ -1156,38 +1156,6 @@ export const WQL = { ); } }, - prepend: implicitQueryAsQL ? ( - - params.setQueryLanguageConfiguration(state => ({ - ...state, - isOpenPopoverImplicitFilter: - !state.isOpenPopoverImplicitFilter, - })) - } - iconType='filter' - > - {implicitQueryAsQL} - - } - isOpen={ - params.queryLanguage.configuration.isOpenPopoverImplicitFilter - } - closePopover={() => - params.setQueryLanguageConfiguration(state => ({ - ...state, - isOpenPopoverImplicitFilter: false, - })) - } - > - - Implicit query: {implicitQueryAsQL} - - This query is added to the input. - - ) : null, // Disable the focus trap in the EuiInputPopover. // This causes when using the Search suggestion, the suggestion popover can be closed. // If this is disabled, then the suggestion popover is open after a short time for this diff --git a/plugins/main/public/components/settings/api/api-table.js b/plugins/main/public/components/settings/api/api-table.js index b0e8a1bd60..53e6056e33 100644 --- a/plugins/main/public/components/settings/api/api-table.js +++ b/plugins/main/public/components/settings/api/api-table.js @@ -370,7 +370,7 @@ export const ApiTable = compose( color="primary" iconType="questionInCircle" aria-label="Info about the error" - onClick={() => this.props.copyToClipBoard(item.downReason)} + onClick={() => this.props.copyToClipBoard(api.error.detail)} /> diff --git a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js index e7f600e6b8..3eeb3c0d62 100644 --- a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js +++ b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js @@ -48,12 +48,10 @@ class WzAgentSelector extends Component { agentTableSearch(agentIdList) { this.closeAgentModal(); if (window.location.href.includes('/agents?')) { - this.location.search( - 'agent', - store.getState().appStateReducers.currentAgentData.id - ? String(store.getState().appStateReducers.currentAgentData.id) - : null, - ); + const seletedAgent = + agentIdList?.[0] || + store.getState().appStateReducers.currentAgentData.id; + this.location.search('agent', seletedAgent ? String(seletedAgent) : null); this.route.reload(); return; } diff --git a/plugins/main/public/components/wz-menu/wz-menu.scss b/plugins/main/public/components/wz-menu/wz-menu.scss index 02afe9d394..7eed9064ed 100644 --- a/plugins/main/public/components/wz-menu/wz-menu.scss +++ b/plugins/main/public/components/wz-menu/wz-menu.scss @@ -244,11 +244,3 @@ md-toolbar .md-button { height: 16px !important; margin-right: 6px; } - -.WzManagementSideMenu { - padding: 16px; -} - -.WzManagementSideMenu span { - font-size: 14px !important; -} diff --git a/plugins/main/public/controllers/agent/components/agents-table.js b/plugins/main/public/controllers/agent/components/agents-table.js index 991168ebda..650b9d7c5f 100644 --- a/plugins/main/public/controllers/agent/components/agents-table.js +++ b/plugins/main/public/controllers/agent/components/agents-table.js @@ -41,15 +41,22 @@ import { get as getLodash } from 'lodash'; import { getCore } from '../../../kibana-services'; import { itHygiene } from '../../../utils/applications'; import { RedirectAppLinks } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; - +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { updateCurrentAgentData } from '../../../redux/actions/appStateActions'; const searchBarWQLOptions = { implicitQuery: { query: 'id!=000', conjunction: ';', }, }; - -export const AgentsTable = withErrorBoundary( +const mapDispatchToProps = dispatch => ({ + updateCurrentAgentData: data => dispatch(updateCurrentAgentData(data)), +}); +export const AgentsTable = compose( + withErrorBoundary, + connect(null, mapDispatchToProps), +)( class AgentsTable extends Component { _isMount = false; constructor(props) { @@ -292,6 +299,7 @@ export const AgentsTable = withErrorBoundary( } return { onClick: ev => { + this.props.updateCurrentAgentData(item); getCore().application.navigateToApp(itHygiene.id, { path: `#/agents?tab=welcome&agent=${item.id}`, }); diff --git a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js index 2b65c18c9d..234923622b 100644 --- a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js +++ b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js @@ -40,6 +40,7 @@ import { getErrorOrchestrator } from '../../../../../react-services/common-servi import { AgentStatus } from '../../../../../components/agents/agent-status'; import { WzRequest } from '../../../../../react-services'; import { itHygiene } from '../../../../../utils/applications'; +import { updateCurrentAgentData } from '../../../../../redux/actions/appStateActions'; class WzGroupAgentsTable extends Component { _isMounted = false; @@ -119,6 +120,7 @@ class WzGroupAgentsTable extends Component { aria-label='Go to the agent' iconType='eye' onClick={async () => { + this.props.updateCurrentAgentData(item); getCore().application.navigateToApp(itHygiene.id, { path: `#/agents?agent=${item.id}`, }); @@ -335,6 +337,7 @@ const mapDispatchToProps = dispatch => { updateSortFieldAgents: sortFieldAgents => dispatch(updateSortFieldAgents(sortFieldAgents)), updateReload: () => dispatch(updateReload()), + updateCurrentAgentData: data => dispatch(updateCurrentAgentData(data)), }; }; diff --git a/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts b/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts new file mode 100644 index 0000000000..80882c4fe7 --- /dev/null +++ b/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts @@ -0,0 +1,20 @@ +import { IWazuhErrorInfo, IWazuhErrorLogOpts } from '../../types'; +import WazuhError from './WazuhError'; + +export class WarningError extends WazuhError { + logOptions: IWazuhErrorLogOpts; + constructor(error: Error, info?: IWazuhErrorInfo) { + super(error, info); + this.logOptions = { + error: { + message: `[${this.constructor.name}]: ${error.message}`, + title: `An warning has occurred`, + error: error, + }, + level: 'WARNING', + severity: 'BUSINESS', + display: true, + store: false, + }; + } +} diff --git a/plugins/main/public/react-services/saved-objects.js b/plugins/main/public/react-services/saved-objects.js index f66fc25321..518875a355 100644 --- a/plugins/main/public/react-services/saved-objects.js +++ b/plugins/main/public/react-services/saved-objects.js @@ -24,6 +24,8 @@ import { } from '../../common/constants'; import { getSavedObjects } from '../kibana-services'; import { webDocumentationLink } from '../../common/services/web_documentation'; +import { ErrorFactory } from './error-management'; +import { WarningError } from './error-management/error-factory/errors/WarningError'; export class SavedObject { /** @@ -322,7 +324,7 @@ export class SavedObject { {}, ); return response.data.fields; - } catch { + } catch (error) { switch (indexType) { case WAZUH_INDEX_TYPE_MONITORING: return FieldsMonitoring; @@ -330,6 +332,12 @@ export class SavedObject { return FieldsStatistics; case WAZUH_INDEX_TYPE_ALERTS: return KnownFields; + default: + const warningError = ErrorFactory.create(WarningError, { + error, + message: error.message, + }); + throw warningError; } } }; diff --git a/plugins/main/public/redux/reducers/appConfigReducers.ts b/plugins/main/public/redux/reducers/appConfigReducers.ts index 5f3d43fd92..cc2879d52d 100644 --- a/plugins/main/public/redux/reducers/appConfigReducers.ts +++ b/plugins/main/public/redux/reducers/appConfigReducers.ts @@ -18,35 +18,48 @@ const initialState: AppConfigState = { isLoading: false, isReady: false, hasError: false, - data: getSettingsDefault(), + data: { + 'vulnerabilities.pattern': 'wazuh-states-vulnerabilities', + 'fim.pattern': 'wazuh-states-fim', + }, }; const appConfigReducer: Reducer = ( state = initialState, - action + action, ) => { + console.log('Current State:', state); + console.log('Action:', action); switch (action.type) { case 'UPDATE_APP_CONFIG_SET_IS_LOADING': return { ...state, isLoading: true, isReady: false, - hasError: false + hasError: false, }; case 'UPDATE_APP_CONFIG_SET_HAS_ERROR': - return { - ...state, - isLoading: false, - isReady: false, - hasError: true - }; + return { + ...state, + isLoading: false, + isReady: false, + hasError: true, + }; case 'UPDATE_APP_CONFIG_DATA': return { ...state, isLoading: false, isReady: true, hasError: false, - data: {...state.data, ...action.payload}, + data: { ...state.data, ...action.payload }, + }; + case 'UPDATE_FIM_PATTERN': + return { + ...state, + data: { + ...state.data, + 'fim.pattern': action.payload, + }, }; default: return state; diff --git a/plugins/main/public/utils/applications.ts b/plugins/main/public/utils/applications.ts index e198f0f7d9..aa675cd06b 100644 --- a/plugins/main/public/utils/applications.ts +++ b/plugins/main/public/utils/applications.ts @@ -76,7 +76,7 @@ export const endpointSumary = { description: i18n.translate('wz-app-endpoints-summary-description', { defaultMessage: 'Summary of agents and their status.', }), - euiIconType: 'usersRolesApp', + euiIconType: 'spacesApp', order: 600, showInOverviewApp: false, showInAgentMenu: false, diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts index 469d3ce014..390e10436b 100644 --- a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts +++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts @@ -76,7 +76,9 @@ describe('getUpdates function', () => { mockedGetWazuhCore.mockImplementation(() => ({ controllers: { WazuhHostsCtrl: jest.fn().mockImplementation(() => ({ - getHostsEntries: jest.fn().mockImplementation(() => [{ id: 'api id' }]), + getHostsEntries: jest + .fn() + .mockImplementation(() => [{ id: 'api id' }]), })), }, services: { @@ -86,6 +88,7 @@ describe('getUpdates function', () => { request: jest.fn().mockImplementation(() => ({ data: { data: { + uuid: '7f828fd6-ef68-4656-b363-247b5861b84c', current_version: '4.3.1', last_available_patch: { description: diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts index 36e377fe02..7f1cf39312 100644 --- a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts +++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts @@ -27,7 +27,7 @@ export const getUpdates = async (checkAvailableUpdates?: boolean): Promise { const data = {}; const method = 'GET'; - const path = '/manager/version/check'; + const path = '/manager/version/check?force_query=true'; const options = { apiHostID: api.id, forceRefresh: true, @@ -42,19 +42,40 @@ export const getUpdates = async (checkAvailableUpdates?: boolean): Promise { + if (update_check === false) { + return API_UPDATES_STATUS.DISABLED; + } + + if ( + last_available_major?.tag || + last_available_minor?.tag || + last_available_patch?.tag + ) { + return API_UPDATES_STATUS.AVAILABLE_UPDATES; + } + + return API_UPDATES_STATUS.UP_TO_DATE; + }; return { - ...update, + current_version, + update_check, + last_available_major, + last_available_minor, + last_available_patch, + last_check_date: last_check_date || undefined, api_id: api.id, - status, + status: getStatus(), }; } catch (e: any) { const error = { diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index 4949a30685..b2cb176ccc 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -50,6 +50,11 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; // Wazuh vulnerabilities export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; +export const WAZUH_INDEX_TYPE_VULNERABILITIES = 'vulnerabilities'; + +// Wazuh fim +export const WAZUH_FIM_PATTERN = 'wazuh-alerts-*'; +export const WAZUH_INDEX_TYPE_FIM = 'fim'; // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; @@ -861,6 +866,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, + 'checks.fim.pattern': { + title: 'Fim index pattern', + description: + 'Enable or disable the fim index pattern health check when opening the app.', + category: SettingCategory.HEALTH_CHECK, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + }, + }, + }, + uiFormTransformChangedInputValue: function ( + value: boolean | string, + ): boolean { + return Boolean(value); + }, + validate: SettingsValidator.isBoolean, + validateBackend: function (schema) { + return schema.boolean(); + }, + }, 'cron.prefix': { title: 'Cron prefix', description: 'Define the index prefix of predefined jobs.', diff --git a/scripts/vulnerabilities-events-injector/DIS_Template.json b/scripts/vulnerabilities-events-injector/DIS_Template.json new file mode 100644 index 0000000000..bb43bc9ab4 --- /dev/null +++ b/scripts/vulnerabilities-events-injector/DIS_Template.json @@ -0,0 +1,348 @@ +{ + "mappings": { + "date_detection": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "message": { + "type": "text" + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "mapping": { + "total_fields": { + "limit": 1000 + } + }, + "refresh_interval": "2s" + } + } +} \ No newline at end of file diff --git a/scripts/vulnerabilities-events-injector/dataInjectScript.py b/scripts/vulnerabilities-events-injector/dataInjectScript.py new file mode 100644 index 0000000000..822612c5f6 --- /dev/null +++ b/scripts/vulnerabilities-events-injector/dataInjectScript.py @@ -0,0 +1,233 @@ +import datetime +from datetime import timedelta +from opensearchpy import OpenSearch, helpers +import random +import json +import os.path +import requests +import warnings + +warnings.filterwarnings("ignore") +def generateRandomDate(): + start_date = datetime.datetime.now() + end_date = start_date - timedelta(days=10) + random_date = start_date + (end_date - start_date) * random.random() + return(random_date.strftime("%Y-%m-%dT%H:%M:%S.{}Z".format(random.randint(0, 999)))) + +def generateRandomAgent(): + agent={} + agent['build'] = {'original':'build{}'.format(random.randint(0, 9999))} + agent['id'] = 'agent{}'.format(random.randint(0, 99)) + agent['name'] = 'Agent{}'.format(random.randint(0, 99)) + agent['version'] = 'v{}-stable'.format(random.randint(0, 9)) + agent['ephemeral_id'] = '{}'.format(random.randint(0, 99999)) + agent['type'] = random.choice(['filebeat','windows','linux','macos']) + return(agent) + +def generateRandomEvent(): + event = {} + event['action'] = random.choice(['login','logout','create','delete','modify','read','write','upload','download','copy','paste','cut','move','rename','open','close','execute','run','install','uninstall','start','stop','kill','suspend','resume','sleep','wake','lock','unlock','encrypt','decrypt','compress','decompress','archive','unarchive','mount','unmount','eject','connect','disconnect','send','receive']) + event['agent_id_status'] = random.choice(['verified','mismatch','missing','auth_metadata_missing']) + event['category'] = random.choice(['authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web']) + event['code'] = '{}'.format(random.randint(0, 99999)) + event['created'] = generateRandomDate() + event['dataset'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web']) + event['duration'] = random.randint(0, 99999) + new_date = generateRandomDate() + while new_date < event['created']: + new_date = generateRandomDate() + event['end'] = new_date + event['hash'] = str(hash('hash{}'.format(random.randint(0, 99999)))) + event['id'] = '{}'.format(random.randint(0, 99999)) + event['ingested'] = generateRandomDate() + event['kind'] = random.choice(['alert','asset','enrichment','event','metric','state','pipeline_error','signal']) + event['module'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web']) + event['original'] = 'original{}'.format(random.randint(0, 99999)) + event['outcome'] = random.choice(['success','failure','unknown']) + event['provider'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web']) + event['reason'] = 'This event happened due to reason{}'.format(random.randint(0, 99999)) + event['reference'] = 'https://system.example.com/event/#{}'.format(random.randint(0, 99999)) + event['risk_score'] = round(random.uniform(0, 10),1) + event['risk_score_norm'] = round(random.uniform(0, 10),1) + event['sequence'] = random.randint(0, 10) + event['severity'] = random.randint(0, 10) + event['start'] = generateRandomDate() + event['timezone'] = random.choice(['UTC','GMT','PST','EST','CST','MST','PDT','EDT','CDT','MDT']) + event['type'] = random.choice(['access','admin','allowed', 'change', 'connection', 'creation', 'deletion', 'denied', 'end', 'error', 'group', 'indicator', 'info', 'installation', 'protocol', 'start', 'user']) + event['url'] = 'http://mysystem.example.com/alert/{}'.format(random.randint(0, 99999)) + return(event) + +def generateRandomHost(): + host = {} + family=random.choice(['debian','ubuntu','macos','ios','android','RHEL']) + version='{}.{}'.format(random.randint(0, 99),random.randint(0, 99)) + host['os'] = { + 'family': family, + 'full': family + ' ' + version, + 'kernel': version+'kernel{}'.format(random.randint(0, 99)), + 'name': family + ' ' + version, + 'platform': family, + 'type': random.choice(['windows','linux','macos','ios','android','unix']), + 'version': version + } + return(host) + + + +def generateRandomLabels(): + labels = {} + labels['label1'] = 'label{}'.format(random.randint(0, 99)) + labels['label2'] = 'label{}'.format(random.randint(0, 99)) + return(labels) + +def generateRandomPackage(): + package = {} + package['architecture'] = random.choice(['x86','x64','arm','arm64']) + package['build_version'] = 'build{}'.format(random.randint(0, 9999)) + package['checksum'] = 'checksum{}'.format(random.randint(0, 9999)) + package['description'] = 'description{}'.format(random.randint(0, 9999)) + package['install_scope'] = random.choice(['user','system']) + package['install_time'] = generateRandomDate() + package['license'] = 'license{}'.format(random.randint(0, 9)) + package['name'] = 'name{}'.format(random.randint(0, 99)) + package['path'] = '/path/to/package{}'.format(random.randint(0, 99)) + package['reference'] = 'package-reference-{}'.format(random.randint(0, 99)) + package['size'] = random.randint(0, 99999) + package['type'] = random.choice(['deb','rpm','msi','pkg','app','apk','exe','zip','tar','gz','7z','rar','cab','iso','dmg','tar.gz','tar.bz2','tar.xz','tar.Z','tar.lz4','tar.sz','tar.zst']) + package['version'] = 'v{}-stable'.format(random.randint(0, 9)) + return(package) + +def generateRandomTags(): + tags = [] + for i in range(0, random.randint(0, 9)): + new_tag = 'tag{}'.format(random.randint(0, 99)) + while new_tag in tags: + new_tag = 'tag{}'.format(random.randint(0, 99)) + tags.append('tag{}'.format(random.randint(0, 99))) + return(tags) + +def generateRandomVulnerability(): + vulnerability = {} + vulnerability['category'] = random.choice(['security','config','os','package','custom']) + vulnerability['classification'] = 'classification{}'.format(random.randint(0, 9999)) + vulnerability['description'] = 'description{}'.format(random.randint(0, 9999)) + vulnerability['enumeration'] = 'CVE' + vulnerability['id'] = 'CVE-{}'.format(random.randint(0, 9999)) + vulnerability['reference'] = 'https://mycve.test.org/cgi-bin/cvename.cgi?name={}'.format(vulnerability['id']) + vulnerability['report_id'] = 'report-{}'.format(random.randint(0, 9999)) + vulnerability['scanner'] = {'vendor':'vendor-{}'.format(random.randint(0, 9))} + vulnerability['score'] = {'base':round(random.uniform(0, 10),1), 'environmental':round(random.uniform(0, 10),1), 'temporal':round(random.uniform(0, 10),1),'version':'{}'.format(round(random.uniform(0, 10),1))} + vulnerability['severity'] = random.choice(['Low','Medium','High','Critical']) + return(vulnerability) + +def generateRandomData(number): + for i in range(0, int(number)): + yield{ + '@timestamp':generateRandomDate(), + 'agent':generateRandomAgent(), + 'ecs':{'version':'1.7.0'}, + 'event':generateRandomEvent(), + 'host':generateRandomHost(), + 'labels':generateRandomLabels(), + 'message':'message{}'.format(random.randint(0, 99999)), + 'package':generateRandomPackage(), + 'tags':generateRandomTags(), + 'vulnerability':generateRandomVulnerability(), + } + +def verifyIndex(index,instance): + if not instance.indices.exists(index): + if os.path.exists('DIS_Template.json'): + print('\nIndex {} does not exist. Trying to create it with the template in DIS_Template.json'.format(index)) + with open('DIS_Template.json') as templateFile: + template = json.load(templateFile) + try: + instance.indices.create(index=index, body=template) + indexExist = True + print('Done!') + except Exception as e: + print('Error: {}'.format(e)) + return True + else: + notemplate=input('\nIndex {} does not exist. Template file not found. Continue without template? (y/n)'.format(index)) + while notemplate != 'y' and notemplate != 'n': + notemplate=input('\nInvalid option. Continue without template? (y/n)') + if notemplate == 'y': + print('\nTrying to create index {} without template'.format(index)) + try: + instance.indices.create(index=index) + print('\nDone!') + except Exception as e: + print('\nError: {}'.format(e)) + return True + return False + +def verifySettings(): + verified = False + if os.path.exists('DIS_Settings.json'): + with open('DIS_Settings.json') as configFile: + config = json.load(configFile) + if 'ip' not in config or 'port' not in config or 'index' not in config or 'username' not in config or 'password' not in config: + print('\nDIS_Settings.json is not properly configured. Continuing without it.') + else: + verified = True + else: + print('\nDIS_Settings.json not found. Continuing without it.') + + if not verified: + ip = input("\nEnter the IP of your Indexer: \n") + port = input("\nEnter the port of your Indexer: \n") + index = input("\nEnter the index name: \n") + url = 'https://{}:{}/{}/_doc'.format(ip, port, index) + username = input("\nUsername: \n") + password = input("\nPassword: \n") + config = json.dumps({'ip':ip,'port':port,'index':index,'username':username,'password':password}) + store = input("\nDo you want to store these settings for future use? (y/n) \n") + while store != 'y' and store != 'n': + store = input("\nInvalid option.\n Do you want to store these settings for future use? (y/n) \n") + if store == 'y': + with open('\nDIS_Settings.json', 'w') as configFile: + configFile.write(config) + return config + +def injectEvents(generator): + config = verifySettings() + + instance = OpenSearch([{'host':config['ip'],'port':config['port']}], http_auth=(config['username'], config['password']), use_ssl=True, verify_certs=False) + + if not instance.ping(): + print('\nError: Could not connect to the indexer') + return + + if (verifyIndex(config['index'],instance)): + print('\nTrying to inject the generated data...\n') + try: + helpers.bulk(instance, generator, index=config['index']) + print('\nDone!') + except Exception as e: + print('\nError: {}'.format(e)) + return + + +def main(): + action = input("Do you want to inject data or save it to a file? (i/s) \n") + while(action != 'i' and action != 's'): + action = input("\nInvalid option.\n Do you want to inject data or save it to a file? (i/s) \n") + number = input("\nHow many events do you want to generate? \n") + while(not number.isdigit()): + number = input("Invalid option.\n How many events do you want to generate? \n") + data = generateRandomData(number) + if action == 's': + print('\nGenerating {} events...\n'.format(number)) + outfile = open('generatedData.json','a') + for i in data: + json.dump(i, outfile) + outfile.write('\n') + outfile.close() + print('\nDone!\n') + else: + injectEvents(data) + return + +if __name__=="__main__": + main() diff --git a/scripts/vulnerabilities-events-injector/readme.md b/scripts/vulnerabilities-events-injector/readme.md new file mode 100644 index 0000000000..3770387005 --- /dev/null +++ b/scripts/vulnerabilities-events-injector/readme.md @@ -0,0 +1,18 @@ +# Vulnerabilities events injector + +This script generates random events with the Vulnerabilities format and saves them to a file or injects them to a Wazuh-Indexer/Opensearch instance. + +# Files + +- `dataInjectScript.py`: The main script file +- `DIS_Template.json`: If the script creates a new index in the instance, it will create it with this template. If this file doesn't exist, it will create it without templates. +- `DIS_Settings.json`: The script can save the Indexer/Opensearch connection parameters. They will be stored in this file. +- `generatedData.json`: If the script is told to save the data to a file, it save it to this file. + +# Usage + +1. Install the requirements with `pip install -r requirements.txt`. For some Operating Systems it will fail and suggest a different way to install it (`sudo pacman -S python-xyz`, `sudo apt install python-xyz`, etc.). + If the package is not found in this way, we can install it running `pip install -r requirements.txt --break-system-packages` (It is recommended to avoid this option if possible) + +2. Run the script with `python3 dataInjectScript.py` +3. Follow the instructions that it will show on the console. diff --git a/scripts/vulnerabilities-events-injector/requirements.txt b/scripts/vulnerabilities-events-injector/requirements.txt new file mode 100644 index 0000000000..7e8ce75a5b --- /dev/null +++ b/scripts/vulnerabilities-events-injector/requirements.txt @@ -0,0 +1 @@ +opensearch_py==2.4.2