diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa684fd75..bf823c2d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.8.1 -## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 03 +## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 05 ### Added @@ -43,21 +43,21 @@ 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) [#6256](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6256) [#6328](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6328) - 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) [#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) [#6231](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6231) [#6246](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6246) [#6321](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6321) [#6338](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6338) [#6356](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6356) +- 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) [#6231](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6231) [#6246](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6246) [#6321](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6321) [#6338](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6338) [#6356](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6356) [#6396](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6396) [#6399](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6399) [#6405](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6405) [#6410](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6410) [#6424](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6424) [#6422](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6422) [#6429](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6429) [#6448](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6448) - 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) -- Added information icon with tooltip on the most active agent in the endpoint summary view [#6364](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6364) +- Added information icon with tooltip on the most active agent in the endpoint summary view [#6364](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6364) [#6421](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6421) - Added a dash with a tooltip in the server APIs table when the run as is disabled [#6354](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6354) ### Changed -- Moved the plugin menu to platform applications into the side menu [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) [#6226](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6226) [#6244](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6244) +- Moved the plugin menu to platform applications into the side menu [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) [#6226](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6226) [#6244](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6244) [#6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) [#6423](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6423) - Changed dashboards. [#6035](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6035) - Change the display order of tabs in all modules. [#6067](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6067) -- Upgraded the `axios` dependency to `1.6.1` [#5062](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5062) +- Upgraded the `axios` dependency to `1.6.1` [#6114](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6114) - Changed the api configuration title in the Server APIs section. [#6373](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6373) -- Changed overview home top KPIs. [#6379](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6379) +- Changed overview home top KPIs. [#6379](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6379) [#6408](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6408) ### Fixed @@ -77,17 +77,27 @@ All notable changes to the Wazuh app project will be documented in this file. - Fixed implicit filter close button in the search bar [#6346](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6346) - Fixed the help menu, to be consistent and avoid duplication [#6374](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6374) - Fixed the axis label visual bug from dashboards [#6378](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6378) +- Fixed a error pop-up spawn in MITRE ATT&CK [#6431](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6431) ### 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) - Removed notice of old Discover deprecation [#6341](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6341) - Removed compilation date field from the app [#6366](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6366) - Removed WAZUH_REGISTRATION_SERVER from Windows agent deployment command [#6361](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6361) +## Wazuh v4.7.3 - OpenSearch Dashboards 2.8.0 - Revision 02 + +### Added + +- Support for Wazuh 4.7.3 + +### Fixed + +- Fixed CDB List import file feature [#6458](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6458) + ## Wazuh v4.7.2 - OpenSearch Dashboards 2.8.0 - Revision 02 ### Added diff --git a/docker/imposter/manager/configuration.js b/docker/imposter/manager/configuration.js index 02984110f2..d85926ec49 100644 --- a/docker/imposter/manager/configuration.js +++ b/docker/imposter/manager/configuration.js @@ -17,7 +17,7 @@ switch (pathConfiguration[0]) { case 'wmodules': respond() .withStatusCode(200) - .withFile('manager/configuration/monitor_reports.json'); + .withFile('manager/configuration/wmodules_wmodules.json'); break; default: diff --git a/docker/osd-dev/dev.yml b/docker/osd-dev/dev.yml index efdf6553bd..c13e118fe0 100755 --- a/docker/osd-dev/dev.yml +++ b/docker/osd-dev/dev.yml @@ -204,8 +204,8 @@ services: mkdir -p /etc/filebeat echo admin | filebeat keystore add username --stdin --force echo ${PASSWORD}| filebeat keystore add password --stdin --force - curl -so /etc/filebeat/wazuh-template.json https://raw.githubusercontent.com/wazuh/wazuh/4.3/extensions/elasticsearch/7.x/wazuh-template.json - curl -s https://packages.wazuh.com/4.x/filebeat/wazuh-filebeat-0.4.tar.gz | tar -xvz -C /usr/share/filebeat/module + curl -so /etc/filebeat/wazuh-template.json https://raw.githubusercontent.com/wazuh/wazuh/v4.7.2/extensions/elasticsearch/7.x/wazuh-template.json + curl -s https://packages.wazuh.com/4.x/filebeat/wazuh-filebeat-0.3.tar.gz | tar -xvz -C /usr/share/filebeat/module # copy filebeat to preserve correct permissions without # affecting host filesystem cp /tmp/filebeat.yml /usr/share/filebeat/filebeat.yml diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js deleted file mode 100644 index 529a0346b2..0000000000 --- a/plugins/main/common/config-equivalences.js +++ /dev/null @@ -1,219 +0,0 @@ -import { ASSETS_PUBLIC_URL, PLUGIN_PLATFORM_NAME } from './constants'; - -export const configEquivalences = { - pattern: - "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", - 'customization.logo.app': `Set the name of the app logo stored at ${ASSETS_PUBLIC_URL}. It is used while the user is logging into Wazuh API.`, - 'customization.logo.healthcheck': `Set the name of the health-check logo stored at ${ASSETS_PUBLIC_URL}`, - 'customization.logo.reports': `Set the name of the reports logo (.png) stored at ${ASSETS_PUBLIC_URL}`, - 'checks.pattern': - 'Enable or disable the index pattern health check when opening the app.', - 'checks.template': - 'Enable or disable the template health check when opening the app.', - 'checks.api': 'Enable or disable the API health check when opening the app.', - 'checks.setup': - 'Enable or disable the setup health check when opening the app.', - 'checks.fields': - 'Enable or disable the known fields health check when opening the app.', - 'checks.metaFields': `Change the default value of the ${PLUGIN_PLATFORM_NAME} metaField configuration`, - 'checks.timeFilter': `Change the default value of the ${PLUGIN_PLATFORM_NAME} timeFilter configuration`, - 'checks.maxBuckets': `Change the default value of the ${PLUGIN_PLATFORM_NAME} max buckets configuration`, - timeout: - 'Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.', - 'ip.selector': - 'Define if the user is allowed to change the selected index pattern directly from the top menu bar.', - 'ip.ignore': - 'Disable certain index pattern names from being available in index pattern selector from the Wazuh app.', - 'wazuh.monitoring.enabled': - 'Enable or disable the wazuh-monitoring index creation and/or visualization.', - 'wazuh.monitoring.frequency': - 'Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.', - 'wazuh.monitoring.shards': - 'Define the number of shards to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.replicas': - 'Define the number of replicas to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.creation': - 'Define the interval in which a new wazuh-monitoring index will be created.', - 'wazuh.monitoring.pattern': - 'Default index pattern to use for Wazuh monitoring.', - hideManagerAlerts: 'Hide the alerts of the manager in every dashboard.', - 'enrollment.dns': - 'Specifies the Wazuh registration server, used for the agent enrollment.', - 'enrollment.password': - 'Specifies the password used to authenticate during the agent enrollment.', - 'cron.prefix': 'Define the index prefix of predefined jobs.', - 'cron.statistics.status': 'Enable or disable the statistics tasks.', - 'cron.statistics.apis': - 'Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.', - 'cron.statistics.interval': - 'Define the frequency of task execution using cron schedule expressions.', - 'cron.statistics.index.name': - 'Define the name of the index in which the documents will be saved.', - 'cron.statistics.index.creation': - 'Define the interval in which a new index will be created.', - 'cron.statistics.index.shards': - 'Define the number of shards to use for the statistics indices.', - 'cron.statistics.index.replicas': - 'Define the number of replicas to use for the statistics indices.', - 'alerts.sample.prefix': - '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.', -}; - -export const nameEquivalence = { - pattern: 'Index pattern', - 'customization.logo.app': 'Logo App', - 'customization.logo.healthcheck': 'Logo Health Check', - 'customization.logo.reports': 'Logo Reports', - 'checks.pattern': 'Index pattern', - 'checks.template': 'Index template', - 'checks.api': 'API connection', - 'checks.setup': 'API version', - 'checks.fields': 'Known fields', - 'checks.metaFields': 'Remove meta fields', - 'checks.timeFilter': 'Set time filter to 24h', - 'checks.maxBuckets': 'Set max buckets to 200000', - timeout: 'Request timeout', - 'ip.selector': 'IP selector', - 'ip.ignore': 'IP ignore', - 'wazuh.monitoring.enabled': 'Status', - 'wazuh.monitoring.frequency': 'Frequency', - 'wazuh.monitoring.shards': 'Index shards', - 'wazuh.monitoring.replicas': 'Index replicas', - 'wazuh.monitoring.creation': 'Index creation', - 'wazuh.monitoring.pattern': 'Index pattern', - hideManagerAlerts: 'Hide manager alerts', - 'enrollment.dns': 'Enrollment DNS', - 'cron.prefix': 'Cron prefix', - 'cron.statistics.status': 'Status', - 'cron.statistics.apis': 'Includes apis', - 'cron.statistics.interval': 'Interval', - 'cron.statistics.index.name': 'Index name', - 'cron.statistics.index.creation': 'Index creation', - 'cron.statistics.index.shards': 'Index shards', - 'cron.statistics.index.replicas': 'Index replicas', - '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'; -const GENERAL = 'General'; -const SECURITY = 'Security'; -const MONITORING = 'Monitoring'; -const STATISTICS = 'Statistics'; -const VULNERABILITIES = 'Vulnerabilities'; -const CUSTOMIZATION = 'Logo Customization'; -export const categoriesNames = [ - HEALTH_CHECK, - GENERAL, - SECURITY, - MONITORING, - STATISTICS, - VULNERABILITIES, - CUSTOMIZATION, -]; - -export const categoriesEquivalence = { - pattern: GENERAL, - 'customization.logo.app': CUSTOMIZATION, - 'customization.logo.healthcheck': CUSTOMIZATION, - 'customization.logo.reports': CUSTOMIZATION, - 'checks.pattern': HEALTH_CHECK, - 'checks.template': HEALTH_CHECK, - 'checks.api': HEALTH_CHECK, - 'checks.setup': HEALTH_CHECK, - 'checks.fields': HEALTH_CHECK, - 'checks.metaFields': HEALTH_CHECK, - 'checks.timeFilter': HEALTH_CHECK, - 'checks.maxBuckets': HEALTH_CHECK, - timeout: GENERAL, - 'ip.selector': GENERAL, - 'ip.ignore': GENERAL, - 'wazuh.monitoring.enabled': MONITORING, - 'wazuh.monitoring.frequency': MONITORING, - 'wazuh.monitoring.shards': MONITORING, - 'wazuh.monitoring.replicas': MONITORING, - 'wazuh.monitoring.creation': MONITORING, - 'wazuh.monitoring.pattern': MONITORING, - hideManagerAlerts: GENERAL, - 'enrollment.dns': GENERAL, - 'cron.prefix': GENERAL, - 'cron.statistics.status': STATISTICS, - 'cron.statistics.apis': STATISTICS, - 'cron.statistics.interval': STATISTICS, - 'cron.statistics.index.name': STATISTICS, - 'cron.statistics.index.creation': STATISTICS, - 'cron.statistics.index.shards': STATISTICS, - 'cron.statistics.index.replicas': STATISTICS, - 'alerts.sample.prefix': GENERAL, - 'vulnerabilities.pattern': VULNERABILITIES, - 'checks.vulnerabilities.pattern': HEALTH_CHECK, -}; - -const TEXT = 'text'; -const NUMBER = 'number'; -const LIST = 'list'; -const BOOLEAN = 'boolean'; -const ARRAY = 'array'; -const INTERVAL = 'interval'; - -export const formEquivalence = { - pattern: { type: TEXT }, - 'customization.logo.app': { type: TEXT }, - 'customization.logo.healthcheck': { type: TEXT }, - 'customization.logo.reports': { type: TEXT }, - 'checks.pattern': { type: BOOLEAN }, - 'checks.template': { type: BOOLEAN }, - 'checks.api': { type: BOOLEAN }, - 'checks.setup': { type: BOOLEAN }, - 'checks.fields': { type: BOOLEAN }, - 'checks.metaFields': { type: BOOLEAN }, - 'checks.timeFilter': { type: BOOLEAN }, - 'checks.maxBuckets': { type: BOOLEAN }, - timeout: { type: NUMBER }, - 'ip.selector': { type: BOOLEAN }, - 'ip.ignore': { type: ARRAY }, - 'wazuh.monitoring.enabled': { type: BOOLEAN }, - 'wazuh.monitoring.frequency': { type: NUMBER }, - 'wazuh.monitoring.shards': { type: NUMBER }, - 'wazuh.monitoring.replicas': { type: NUMBER }, - 'wazuh.monitoring.creation': { - type: LIST, - params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ], - }, - }, - 'wazuh.monitoring.pattern': { type: TEXT }, - hideManagerAlerts: { type: BOOLEAN }, - 'enrollment.dns': { type: TEXT }, - 'cron.prefix': { type: TEXT }, - 'cron.statistics.status': { type: BOOLEAN }, - 'cron.statistics.apis': { type: ARRAY }, - 'cron.statistics.interval': { type: INTERVAL }, - 'cron.statistics.index.name': { type: TEXT }, - 'cron.statistics.index.creation': { - type: LIST, - params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ], - }, - }, - 'cron.statistics.index.shards': { type: NUMBER }, - 'cron.statistics.index.replicas': { type: NUMBER }, - 'alerts.sample.prefix': { type: TEXT }, - 'vulnerabilities.pattern': { type: TEXT }, - 'checks.vulnerabilities.pattern': { type: BOOLEAN }, -}; diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index d1a9ea4fd3..0d30df7489 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -51,6 +51,10 @@ 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'; +export const VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER = { + enabled: 'wazuh.cluster.name', + disabled: 'wazuh.manager.name', +}; // Wazuh fim export const WAZUH_FIM_PATTERN = 'wazuh-states-fim'; @@ -230,6 +234,7 @@ export enum WAZUH_MENU_SETTINGS_SECTIONS_ID { } export const AUTHORIZED_AGENTS = 'authorized-agents'; +export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'exclude-server'; // Wazuh links export const WAZUH_LINK_GITHUB = 'https://github.com/wazuh'; @@ -809,60 +814,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, - 'checks.vulnerabilities.pattern': { - title: 'Vulnerabilities index pattern', - description: - 'Enable or disable the vulnerabilities index pattern health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: false, - 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(); - }, - }, - '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/common/plugin-settings.test.ts b/plugins/main/common/plugin-settings.test.ts index 959f5f9d7d..27b39a1240 100644 --- a/plugins/main/common/plugin-settings.test.ts +++ b/plugins/main/common/plugin-settings.test.ts @@ -34,8 +34,6 @@ describe('[settings] Input validation', () => { ${'checks.template'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} ${'checks.timeFilter'} | ${true} | ${undefined} ${'checks.timeFilter'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} - ${'checks.vulnerabilities.pattern'} | ${true} | ${undefined} - ${'checks.vulnerabilities.pattern'} | ${0} | ${'It should be a boolean. Allowed values: true or false.'} ${'cron.prefix'} | ${'test'} | ${undefined} ${'cron.prefix'} | ${'test space'} | ${'No whitespaces allowed.'} ${'cron.prefix'} | ${''} | ${'Value can not be empty.'} diff --git a/plugins/main/public/components/common/hocs/withGuard.tsx b/plugins/main/public/components/common/hocs/withGuard.tsx index 1a2c689f5e..e29d42ba7a 100644 --- a/plugins/main/public/components/common/hocs/withGuard.tsx +++ b/plugins/main/public/components/common/hocs/withGuard.tsx @@ -9,8 +9,68 @@ * * Find more information about this on the LICENSE file. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; -export const withGuard = (condition, ComponentFulfillsCondition) => WrappedComponent => props => { - return condition(props) ? : -} \ No newline at end of file +export const withGuard = + (condition: (props: any) => boolean, ComponentFulfillsCondition) => + WrappedComponent => + props => { + return condition(props) ? ( + + ) : ( + + ); + }; + +export const withGuardAsync = + ( + condition: (props: any) => { ok: boolean; data: any }, + ComponentFulfillsCondition: React.JSX.Element, + ComponentLoadingResolution: null | React.JSX.Element = null, + ) => + WrappedComponent => + props => { + const [loading, setLoading] = useState(true); + const [fulfillsCondition, setFulfillsCondition] = useState({ + ok: false, + data: {}, + }); + + const execCondition = async () => { + try { + setLoading(true); + setFulfillsCondition({ ok: false, data: {} }); + setFulfillsCondition( + await condition({ ...props, check: execCondition }), + ); + } catch (error) { + setFulfillsCondition({ ok: false, data: { error } }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + execCondition(); + }, []); + + if (loading) { + return ComponentLoadingResolution ? ( + + ) : null; + } + + return fulfillsCondition.ok ? ( + + ) : ( + + ); + }; diff --git a/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx b/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx new file mode 100644 index 0000000000..36c4b49930 --- /dev/null +++ b/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + EuiTitle, + EuiPanel, + EuiEmptyPrompt, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; +import { IntlProvider } from 'react-intl'; + +export function LoadingSpinnerDataSource() { + return ( + + + } + title={ + +

+ +

+
+ } + /> +
+
+ ); +} diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js index d3ad9e8089..7b1705b0e4 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.js @@ -41,6 +41,8 @@ import { githubColumns } from '../../overview/github-panel/events/github-columns import { mitreAttackColumns } from '../../overview/mitre/events/mitre-attack-columns'; import { virustotalColumns } from '../../overview/virustotal/events/virustotal-columns'; import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns'; +import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; +import { withVulnerabilitiesStateDataSource } from '../../overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern'; const DashboardTab = { id: 'dashboard', @@ -194,17 +196,33 @@ export const ModulesDefaults = { id: 'dashboard', name: 'Dashboard', component: DashboardVuls, - buttons: [ButtonModuleExploreAgent], + /* For ButtonModuleExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ + buttons: [ + ({ ...props }) => ( + + ), + ], }, { id: 'inventory', name: 'Inventory', component: InventoryVuls, - buttons: [ButtonModuleExploreAgent], + /* For ButtonModuleExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ + buttons: [ + ({ ...props }) => ( + + ), + ], }, { ...renderDiscoverTab(ALERTS_INDEX_PATTERN, vulnerabilitiesColumns), - component: withModuleNotForAgent(() => ( + component: withVulnerabilitiesStateDataSource(() => ( { }); it('should return the same filters and apply them to the filter manager when are received by props', async () => { + const exampleIndexPatternId = 'wazuh-index-pattern'; const defaultFilters: Filter[] = [ { query: 'something to filter', @@ -183,6 +184,7 @@ describe('[hook] useSearchBarConfiguration', () => { .mockReturnValue(defaultFilters); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ + defaultIndexPatternID: exampleIndexPatternId, filters: defaultFilters, }), ); diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index 40a7488210..5f88d36919 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -10,10 +10,10 @@ import { } from '../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../kibana-services'; import { useFilterManager, useQueryManager, useTimeFilter } from '../hooks'; -import { AUTHORIZED_AGENTS } from '../../../../common/constants'; -import { AppState } from '../../../react-services/app-state'; -import { getSettingDefaultValue } from '../../../../common/services/settings'; -import { FilterStateStore } from '../../../../../../src/plugins/data/common'; +import { + AUTHORIZED_AGENTS, + DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, +} from '../../../../common/constants'; // Input - types type tUseSearchBarCustomInputs = { @@ -23,6 +23,17 @@ type tUseSearchBarCustomInputs = { payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean, ) => void; + onMount?: ( + filterManager: FilterManager, + defaultIndexPatternID: string, + ) => void; + onUpdate?: (filters: Filter[], filterManager: FilterManager) => void; + onUnMount?: ( + previousFilters: Filter[], + toIndexPattern: string | null, + filterManager: FilterManager, + defaultIndexPatternID: string, + ) => void; }; type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; @@ -36,12 +47,14 @@ type tUserSearchBarResponse = { * @param props * @returns */ -const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { +const useSearchBarConfiguration = ( + props?: tUseSearchBarProps, +): tUserSearchBarResponse => { // dependencies const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters'; const SESSION_STORAGE_PREV_FILTER_NAME = 'wazuh_persistent_current_filter'; const filterManager = useFilterManager().filterManager as FilterManager; - const { filters } = useFilterManager(); + const filters = props?.filters ? props.filters : filterManager.getFilters(); const [query, setQuery] = props?.query ? useState(props?.query) : useQueryManager(); @@ -52,43 +65,26 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { useState(); useEffect(() => { - const prevPattern = - AppState.getCurrentPattern() || getSettingDefaultValue('pattern'); - if (filters && filters.length > 0) { - sessionStorage.setItem( - SESSION_STORAGE_FILTERS_NAME, - JSON.stringify( - updatePrevFilters(filters, props?.defaultIndexPatternID), - ), - ); - } - sessionStorage.setItem(SESSION_STORAGE_PREV_FILTER_NAME, prevPattern); - AppState.setCurrentPattern(props?.defaultIndexPatternID); initSearchBar(); - - /** - * When the component is unmounted, the original filters that arrived - * when the component was mounted are added. - * Both when the component is mounted and unmounted, the index pattern is - * updated so that the pin action adds the agent with the correct index pattern. - */ return () => { + /* Upon unmount, the previous filters are restored */ const prevStoragePattern = sessionStorage.getItem( SESSION_STORAGE_PREV_FILTER_NAME, ); - AppState.setCurrentPattern(prevStoragePattern); sessionStorage.removeItem(SESSION_STORAGE_PREV_FILTER_NAME); const storagePreviousFilters = sessionStorage.getItem( SESSION_STORAGE_FILTERS_NAME, ); if (storagePreviousFilters) { const previousFilters = JSON.parse(storagePreviousFilters); - const cleanedFilters = cleanFilters( - previousFilters, - prevStoragePattern ?? prevPattern, - ); - filterManager.setFilters(cleanedFilters); - sessionStorage.removeItem(SESSION_STORAGE_FILTERS_NAME); + if (props?.onUnMount) { + props.onUnMount( + previousFilters, + prevStoragePattern, + filterManager, + props?.defaultIndexPatternID, + ); + } } }; }, []); @@ -100,8 +96,11 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { setIsLoading(true); const indexPattern = await getIndexPattern(props?.defaultIndexPatternID); setIndexPatternSelected(indexPattern); - const initialFilters = props?.filters ?? filters; - filterManager.setFilters(initialFilters); + if (props?.onMount) { + props.onMount(filterManager, props?.defaultIndexPatternID); + } else { + filterManager.setFilters(filters); + } setIsLoading(false); }; @@ -126,199 +125,34 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { } }; - const updatePrevFilters = ( - previousFilters: Filter[], - indexPattern?: string, - ) => { - const pinnedAgent = previousFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - if (!pinnedAgent) { - const url = window.location.href; - const regex = new RegExp('agentId=' + '[^&]*'); - const match = url.match(regex); - if (match && match[0]) { - const agentId = match[0].split('=')[1]; - const agentFilters = previousFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - const insertPinnedAgent = { - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: indexPattern, - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { store: 'appState', isImplicit: true }, - }; - agentFilters.push(insertPinnedAgent); - return agentFilters; - } - } - if (pinnedAgent) { - const agentFilters = previousFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - agentFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: indexPattern, - }, - $state: { store: FilterStateStore.APP_STATE, isImplicit: true }, - }); - return agentFilters; - } - return previousFilters; - }; - - /** - * 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 getFilters = () => { - const originalFilters = filterManager ? filterManager.getFilters() : []; - const pinnedAgent = originalFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - const mappedFilters = originalFilters.filter( - (filter: Filter) => - filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters - filter?.meta?.index === props?.defaultIndexPatternID, - ); - - if (pinnedAgent) { - const agentFilters = mappedFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - agentFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: props?.defaultIndexPatternID, - }, - }); - return agentFilters; - } - return mappedFilters; - }; - - /** - * 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. - * This cleanup adjusts the index pattern of a pinned agent, if applicable. - * @param previousFilters - * @returns - */ - const cleanFilters = (previousFilters: Filter[], indexPattern?: string) => { - /** - * Verify if a pinned agent exists, identifying it by its meta.isImplicit attribute or by the agentId query param URL. - * We also compare the agent.id filter with the agentId query param because the OSD filter definition does not include the "isImplicit" attribute that Wazuh adds. - * There may be cases where the "isImplicit" attribute is lost, since any action regarding filters that is done with the - * filterManager ( addFilters, setFilters, setGlobalFilters, setAppFilters) - * does a mapAndFlattenFilters mapping to the filters that removes any attributes that are not part of the filter definition. - * */ - const mappedFilters = previousFilters.filter( - (filter: Filter) => - filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters - filter?.meta?.index !== props?.defaultIndexPatternID, - ); - const pinnedAgent = mappedFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - - if (!pinnedAgent) { - const url = window.location.href; - const regex = new RegExp('agentId=' + '[^&]*'); - const match = url.match(regex); - if (match && match[0]) { - const agentId = match[0].split('=')[1]; - const agentFilters = mappedFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - const insertPinnedAgent = { - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: indexPattern, - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { store: 'appState', isImplicit: true }, - }; - agentFilters.push(insertPinnedAgent); - return agentFilters; - } - } - if (pinnedAgent) { - mappedFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: indexPattern, - }, - $state: { store: FilterStateStore.APP_STATE, isImplicit: true }, - }); - } - return mappedFilters; - }; - /** * Search bar properties necessary to render and initialize the osd search bar component */ const searchBarProps: Partial = { isLoading, ...(indexPatternSelected && { indexPatterns: [indexPatternSelected] }), // indexPattern cannot be empty or empty [] - filters: getFilters(), + filters: filters + .filter( + (filter: Filter) => + ![ + AUTHORIZED_AGENTS, + DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, + ].includes(filter?.meta?.controlledBy), // remove auto loaded agent.id filters + ) + .sort((a: Filter, b: Filter) => { + return a?.$state?.isImplicit && !(a?.meta?.key === 'agent.id') + ? -1 + : b?.$state?.isImplicit + ? 1 + : -1; + }), query, timeHistory, dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (filters: Filter[]) => { - 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, - props?.defaultIndexPatternID, - ); - filterManager.setFilters([...cleanedFilters, ...filters]); - - props?.onFiltersUpdated && - props?.onFiltersUpdated([...cleanedFilters, ...filters]); + if (props?.onUpdate) { + props.onUpdate(filters, filterManager, props?.onFiltersUpdated); } else { filterManager.setFilters(filters); props?.onFiltersUpdated && props?.onFiltersUpdated(filters); @@ -343,4 +177,4 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { }; }; -export default useSearchBar; +export default useSearchBarConfiguration; diff --git a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap index 9e6a4d67a0..95a6d301b3 100644 --- a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap +++ b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap @@ -105,40 +105,6 @@ exports[`Health Check container should render a Health check screen 1`] = ` title="Check statistics index pattern" validationService={[Function]} /> - - ({ 'checks.pattern': true, 'checks.template': true, 'checks.fields': true, - 'checks.vulnerabilities.pattern': true, - 'checks.fim.pattern': true, }, }), useRootScope: () => ({}), 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 de9013fdac..a6bff82311 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 @@ -33,11 +33,8 @@ import { withErrorBoundary, withReduxProvider } from '../../common/hocs'; import { getCore, getHttp, getWzCurrentAppID } from '../../../kibana-services'; import { HEALTH_CHECK_REDIRECTION_TIME, - NOT_TIME_FIELD_NAME_INDEX_PATTERN, WAZUH_INDEX_TYPE_MONITORING, WAZUH_INDEX_TYPE_STATISTICS, - WAZUH_INDEX_TYPE_VULNERABILITIES, - WAZUH_INDEX_TYPE_FIM, } from '../../../../common/constants'; import { compose } from 'redux'; @@ -91,32 +88,6 @@ const checks = { shouldCheck: true, canRetry: true, }, - 'vulnerabilities.pattern': { - title: 'Check vulnerabilities index pattern', - label: 'Vulnerabilities index pattern', - validator: appConfig => - checkPatternSupportService( - appConfig.data['vulnerabilities.pattern'], - WAZUH_INDEX_TYPE_VULNERABILITIES, - NOT_TIME_FIELD_NAME_INDEX_PATTERN, - ), - awaitFor: [], - 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/overview/mitre/components/tactics/tactics.tsx b/plugins/main/public/components/overview/mitre/components/tactics/tactics.tsx index f89e4a28fd..24f183ec4f 100644 --- a/plugins/main/public/components/overview/mitre/components/tactics/tactics.tsx +++ b/plugins/main/public/components/overview/mitre/components/tactics/tactics.tsx @@ -20,10 +20,10 @@ import { EuiButtonIcon, EuiLoadingSpinner, EuiContextMenu, - EuiIcon -} from '@elastic/eui' + EuiIcon, +} from '@elastic/eui'; import { IFilterParams, getElasticAlerts } from '../../lib'; -import { getToasts } from '../../../../../kibana-services'; +import { getToasts } from '../../../../../kibana-services'; import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../../../react-services/common-services'; @@ -75,21 +75,30 @@ export class Tactics extends Component { } shouldComponentUpdate(nextProps, nextState) { - const { filterParams, indexPattern, selectedTactics, isLoading } = this.props; + const { filterParams, indexPattern, selectedTactics, isLoading } = + this.props; const { tacticsCount, loadingAlerts } = this.state; if (nextState.loadingAlerts !== loadingAlerts) return true; if (nextProps.isLoading !== isLoading) return true; - if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) return true; - if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) return true; - if (JSON.stringify(nextState.tacticsCount) !== JSON.stringify(tacticsCount)) return true; - if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) return true; + if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) + return true; + if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) + return true; + if (JSON.stringify(nextState.tacticsCount) !== JSON.stringify(tacticsCount)) + return true; + if ( + JSON.stringify(nextState.selectedTactics) !== + JSON.stringify(selectedTactics) + ) + return true; return false; } async componentDidUpdate(prevProps) { const { isLoading, tacticsObject } = this.props; if ( - JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || + JSON.stringify(prevProps.tacticsObject) !== + JSON.stringify(tacticsObject) || isLoading !== prevProps.isLoading ) { this.getTacticsCount(this.state.firstTime); @@ -125,12 +134,16 @@ export class Tactics extends Component { // TODO: use `status` and `statusText` to show errors // @ts-ignore const { data } = await getElasticAlerts(indexPattern, filterParams, aggs); - const { buckets } = data.aggregations.tactics; + const buckets = data?.aggregations?.tactics?.buckets || []; if (firstTime) { - this.initTactics(buckets); // top tactics are checked on component mount + this.initTactics(); // top tactics are checked on component mount } this._isMount && - this.setState({ tacticsCount: buckets, loadingAlerts: false, firstTime: false }); + this.setState({ + tacticsCount: buckets, + loadingAlerts: false, + firstTime: false, + }); } catch (error) { const options = { context: `${Tactics.name}.getTacticsCount`, @@ -154,7 +167,8 @@ export class Tactics extends Component { } facetClicked(id) { - const { selectedTactics: oldSelected, onChangeSelectedTactics } = this.props; + const { selectedTactics: oldSelected, onChangeSelectedTactics } = + this.props; const selectedTactics = { ...oldSelected, [id]: !oldSelected[id], @@ -166,13 +180,14 @@ export class Tactics extends Component { const { tacticsCount } = this.state; const { selectedTactics } = this.props; const tacticsIds = Object.keys(this.props.tacticsObject); - const tacticsList: Array = tacticsIds.map((item) => { - const quantity = (tacticsCount.find((tactic) => tactic.key === item) || {}).doc_count || 0; + const tacticsList: Array = tacticsIds.map(item => { + const quantity = + (tacticsCount.find(tactic => tactic.key === item) || {}).doc_count || 0; return { id: item, label: item, quantity, - onClick: (id) => this.facetClicked(id), + onClick: id => this.facetClicked(id), }; }); @@ -180,7 +195,7 @@ export class Tactics extends Component { <> {tacticsList .sort((a, b) => b.quantity - a.quantity) - .map((facet) => { + .map(facet => { let iconNode; return ( facet.onClick(facet.id) : undefined} + onClick={ + facet.onClick ? () => facet.onClick(facet.id) : undefined + } > {facet.label} @@ -203,7 +220,7 @@ export class Tactics extends Component { checkAllChecked(tacticList: any[]) { const { selectedTactics } = this.props; let allSelected = true; - tacticList.forEach((item) => { + tacticList.forEach(item => { if (!selectedTactics[item.id]) allSelected = false; }); @@ -215,7 +232,7 @@ export class Tactics extends Component { onCheckAllClick() { const allSelected = !this.state.allSelected; const { selectedTactics, onChangeSelectedTactics } = this.props; - Object.keys(selectedTactics).map((item) => { + Object.keys(selectedTactics).map(item => { selectedTactics[item] = allSelected; }); @@ -233,7 +250,7 @@ export class Tactics extends Component { selectAll(status) { const { selectedTactics, onChangeSelectedTactics } = this.props; - Object.keys(selectedTactics).map((item) => { + Object.keys(selectedTactics).map(item => { selectedTactics[item] = status; }); onChangeSelectedTactics(selectedTactics); @@ -247,7 +264,7 @@ export class Tactics extends Component { items: [ { name: 'Select all', - icon: , + icon: , onClick: () => { this.closePopover(); this.selectAll(true); @@ -255,7 +272,7 @@ export class Tactics extends Component { }, { name: 'Unselect all', - icon: , + icon: , onClick: () => { this.closePopover(); this.selectAll(false); @@ -265,25 +282,34 @@ export class Tactics extends Component { }, ]; return ( -
+
- +

Tactics

- + this.onGearButtonClick()} aria-label={'tactics options'} > } isOpen={this.state.isPopoverOpen} - panelPaddingSize="none" + panelPaddingSize='none' closePopover={() => this.closePopover()} > @@ -292,7 +318,7 @@ export class Tactics extends Component {
{this.props.isLoading ? ( - + ) : ( {this.getTacticsList()} diff --git a/plugins/main/public/components/overview/mitre/components/techniques/techniques.tsx b/plugins/main/public/components/overview/mitre/components/techniques/techniques.tsx index 50c4f10b28..2daf6834cb 100644 --- a/plugins/main/public/components/overview/mitre/components/techniques/techniques.tsx +++ b/plugins/main/public/components/overview/mitre/components/techniques/techniques.tsx @@ -85,11 +85,21 @@ export const Techniques = withWindowSize( } shouldComponentUpdate(nextProps, nextState) { - const { filterParams, indexPattern, selectedTactics, isLoading } = this.props; + const { filterParams, indexPattern, selectedTactics, isLoading } = + this.props; if (nextProps.isLoading !== isLoading) return true; - if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) return true; - if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) return true; - if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) + if ( + JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams) + ) + return true; + if ( + JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern) + ) + return true; + if ( + JSON.stringify(nextState.selectedTactics) !== + JSON.stringify(selectedTactics) + ) return true; return false; } @@ -97,9 +107,11 @@ export const Techniques = withWindowSize( componentDidUpdate(prevProps) { const { isLoading, tacticsObject, filters } = this.props; if ( - JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || + JSON.stringify(prevProps.tacticsObject) !== + JSON.stringify(tacticsObject) || isLoading !== prevProps.isLoading || - JSON.stringify(prevProps.filterParams) !== JSON.stringify(this.props.filterParams) + JSON.stringify(prevProps.filterParams) !== + JSON.stringify(this.props.filterParams) ) this.getTechniquesCount(); } @@ -108,7 +120,12 @@ export const Techniques = withWindowSize( this._isMount = false; } - showToast(color: string, title: string = '', text: string = '', time: number = 3000) { + showToast( + color: string, + title: string = '', + text: string = '', + time: number = 3000, + ) { getToasts().add({ color: color, title: title, @@ -134,9 +151,14 @@ export const Techniques = withWindowSize( this._isMount && this.setState({ loadingAlerts: true }); // TODO: use `status` and `statusText` to show errors // @ts-ignore - const { data, status, statusText } = await getElasticAlerts(indexPattern, filters, aggs); - const { buckets } = data.aggregations.techniques; - this._isMount && this.setState({ techniquesCount: buckets, loadingAlerts: false }); + const { data, status, statusText } = await getElasticAlerts( + indexPattern, + filters, + aggs, + ); + const buckets = data?.aggregations?.techniques?.buckets || []; + this._isMount && + this.setState({ techniquesCount: buckets, loadingAlerts: false }); } catch (error) { const options = { context: `${Techniques.name}.getTechniquesCount`, @@ -163,23 +185,31 @@ export const Techniques = withWindowSize( items: [ { name: 'Filter for value', - icon: , + icon: , onClick: () => { this.closeActionsMenu(); - this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + this.addFilter({ + key: 'rule.mitre.id', + value: techniqueID, + negate: false, + }); }, }, { name: 'Filter out value', - icon: , + icon: , onClick: () => { this.closeActionsMenu(); - this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: true }); + this.addFilter({ + key: 'rule.mitre.id', + value: techniqueID, + negate: true, + }); }, }, { name: 'View technique details', - icon: , + icon: , onClick: () => { this.closeActionsMenu(); this.showFlyout(techniqueID); @@ -192,7 +222,11 @@ export const Techniques = withWindowSize( techniqueColumnsResponsive() { if (this.props && this.props.windowSize) { - return this.props.windowSize.width < 930 ? 2 : this.props.windowSize.width < 1200 ? 3 : 4; + return this.props.windowSize.width < 930 + ? 2 + : this.props.windowSize.width < 1200 + ? 3 + : 4; } else { return 4; } @@ -224,10 +258,16 @@ export const Techniques = withWindowSize( const params = { limit: limitResults }; this.setState({ isSearching: true }); const output = await this.getMitreTechniques(params); - const totalItems = (((output || {}).data || {}).data || {}).total_affected_items; + const totalItems = (((output || {}).data || {}).data || {}) + .total_affected_items; let mitreTechniques = []; mitreTechniques.push(...output.data.data.affected_items); - if (totalItems && output.data && output.data.data && totalItems > limitResults) { + if ( + totalItems && + output.data && + output.data.data && + totalItems > limitResults + ) { const extraResults = await Promise.all( Array(Math.ceil((totalItems - params.limit) / params.limit)) .fill() @@ -237,7 +277,7 @@ export const Techniques = withWindowSize( offset: limitResults * (1 + index), }); return response.data.data.affected_items; - }) + }), ); mitreTechniques.push(...extraResults.flat()); } @@ -246,16 +286,22 @@ export const Techniques = withWindowSize( buildObjTechniques(techniques) { const techniquesObj = []; - techniques.forEach((element) => { - const mitreObj = this.state.mitreTechniques.find((item) => item.id === element); + techniques.forEach(element => { + const mitreObj = this.state.mitreTechniques.find( + item => item.id === element, + ); if (mitreObj) { const mitreTechniqueName = mitreObj.name; const mitreTechniqueID = mitreObj.source === MITRE_ATTACK ? mitreObj.external_id - : mitreObj.references.find((item) => item.source === MITRE_ATTACK).external_id; + : mitreObj.references.find(item => item.source === MITRE_ATTACK) + .external_id; mitreTechniqueID - ? techniquesObj.push({ id: mitreTechniqueID, name: mitreTechniqueName }) + ? techniquesObj.push({ + id: mitreTechniqueID, + name: mitreTechniqueName, + }) : ''; } }); @@ -268,47 +314,59 @@ export const Techniques = withWindowSize( let hash = {}; let tacticsToRender: Array = []; const currentTechniques = Object.keys(tacticsObject) - .map((tacticsKey) => ({ + .map(tacticsKey => ({ tactic: tacticsKey, - techniques: this.buildObjTechniques(tacticsObject[tacticsKey].techniques), + techniques: this.buildObjTechniques( + tacticsObject[tacticsKey].techniques, + ), })) - .filter((tactic) => this.props.selectedTactics[tactic.tactic]) - .map((tactic) => tactic.techniques) + .filter(tactic => this.props.selectedTactics[tactic.tactic]) + .map(tactic => tactic.techniques) .flat() - .filter((techniqueID, index, array) => array.indexOf(techniqueID) === index); + .filter( + (techniqueID, index, array) => array.indexOf(techniqueID) === index, + ); tacticsToRender = currentTechniques - .filter((technique) => + .filter(technique => this.state.filteredTechniques ? this.state.filteredTechniques.includes(technique.id) : technique.id && hash[technique.id] ? false - : (hash[technique.id] = true) + : (hash[technique.id] = true), ) - .map((technique) => { + .map(technique => { return { id: technique.id, label: `${technique.id} - ${technique.name}`, quantity: - (techniquesCount.find((item) => item.key === technique.id) || {}).doc_count || 0, + (techniquesCount.find(item => item.key === technique.id) || {}) + .doc_count || 0, }; }) - .filter((technique) => (this.state.hideAlerts ? technique.quantity !== 0 : true)); + .filter(technique => + this.state.hideAlerts ? technique.quantity !== 0 : true, + ); const tacticsToRenderOrdered = tacticsToRender .sort((a, b) => b.quantity - a.quantity) .map((item, idx) => { const tooltipContent = `View details of ${item.label} (${item.id})`; const toolTipAnchorClass = - 'wz-display-inline-grid' + (this.state.hover === item.id ? ' wz-mitre-width' : ' '); + 'wz-display-inline-grid' + + (this.state.hover === item.id ? ' wz-mitre-width' : ' '); return ( this.setState({ hover: item.id })} onMouseLeave={() => this.setState({ hover: '' })} key={idx} - style={{ border: '1px solid #8080804a', maxWidth: 'calc(25% - 8px)', maxHeight: 41 }} + style={{ + border: '1px solid #8080804a', + maxWidth: 'calc(25% - 8px)', + maxHeight: 41, + }} > this.showFlyout(item.id)} > @@ -340,25 +398,31 @@ export const Techniques = withWindowSize( {this.state.hover === item.id && ( - + { + onClick={e => { this.openDashboard(e, item.id); e.stopPropagation(); }} - color="primary" - type="visualizeApp" + color='primary' + type='visualizeApp' > {' '}   - + { + onClick={e => { this.openDiscover(e, item.id); e.stopPropagation(); }} - color="primary" - type="discoverApp" + color='primary' + type='discoverApp' > @@ -367,19 +431,28 @@ export const Techniques = withWindowSize( } isOpen={this.state.actionsOpen === item.id} closePopover={() => this.closeActionsMenu()} - panelPaddingSize="none" + panelPaddingSize='none' style={{ width: '100%' }} - anchorPosition="downLeft" + anchorPosition='downLeft' > - + ); }); - if (this.state.isSearching || this.state.loadingAlerts || this.props.isLoading) { + if ( + this.state.isSearching || + this.state.loadingAlerts || + this.props.isLoading + ) { return ( - - + + ); } @@ -387,7 +460,7 @@ export const Techniques = withWindowSize( return ( + ); } } openDiscover(e, techniqueID) { - this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + this.addFilter({ + key: 'rule.mitre.id', + value: techniqueID, + negate: false, + }); this.props.onSelectedTabChanged('events'); } openDashboard(e, techniqueID) { - this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + this.addFilter({ + key: 'rule.mitre.id', + value: techniqueID, + negate: false, + }); this.props.onSelectedTabChanged('dashboard'); } @@ -430,7 +515,8 @@ export const Techniques = withWindowSize( params: { query: filter.value }, type: 'phrase', negate: filter.negate || false, - index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), + index: + AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, query: { match_phrase: matchPhrase }, $state: { store: 'appState' }, @@ -438,13 +524,14 @@ export const Techniques = withWindowSize( filterManager.addFilters([newFilter]); } - onChange = (searchValue) => { + onChange = searchValue => { if (!searchValue) { - this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + this._isMount && + this.setState({ filteredTechniques: false, isSearching: false }); } }; - onSearch = async (searchValue) => { + onSearch = async searchValue => { try { if (searchValue) { this._isMount && this.setState({ isSearching: true }); @@ -453,12 +540,18 @@ export const Techniques = withWindowSize( search: searchValue, }, }); - const filteredTechniques = (((response || {}).data || {}).data.affected_items || []).map( - (item) => [item].filter((reference) => reference.source === MITRE_ATTACK)[0].external_id + const filteredTechniques = ( + ((response || {}).data || {}).data.affected_items || [] + ).map( + item => + [item].filter(reference => reference.source === MITRE_ATTACK)[0] + .external_id, ); - this._isMount && this.setState({ filteredTechniques, isSearching: false }); + this._isMount && + this.setState({ filteredTechniques, isSearching: false }); } else { - this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + this._isMount && + this.setState({ filteredTechniques: false, isSearching: false }); } } catch (error) { const options = { @@ -474,7 +567,8 @@ export const Techniques = withWindowSize( }, }; getErrorOrchestrator().handleError(options); - this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + this._isMount && + this.setState({ filteredTechniques: false, isSearching: false }); } }; async closeActionsMenu() { @@ -507,7 +601,7 @@ export const Techniques = withWindowSize(
- +

Techniques

@@ -518,26 +612,26 @@ export const Techniques = withWindowSize( Hide techniques with no alerts   this.hideAlerts()} + onChange={e => this.hideAlerts()} />
- + - +
{this.renderFacet()}
@@ -552,5 +646,5 @@ export const Techniques = withWindowSize(
); } - } + }, ); diff --git a/plugins/main/public/components/overview/vulnerabilities/common/components/check-module-enabled.tsx b/plugins/main/public/components/overview/vulnerabilities/common/components/check-module-enabled.tsx new file mode 100644 index 0000000000..f254dbde95 --- /dev/null +++ b/plugins/main/public/components/overview/vulnerabilities/common/components/check-module-enabled.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react'; +import { + clusterReq, + clusterNodes, +} from '../../../../../controllers/management/components/management/configuration/utils/wz-fetch'; +import { WzRequest } from '../../../../../react-services'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../../../react-services/common-services'; +import { useUserPermissionsRequirements } from '../../../../common/hooks'; + +async function checkVDIsEnabledCluster() { + // Get nodes + const responseNodes = await clusterNodes(); + + const nodes = responseNodes.data.data.affected_items.map(({ name }) => name); + + // Check if at least some of the nodes has the module enabled + for (const node of nodes) { + const responseNodeWmodules = await WzRequest.apiReq( + 'GET', + `/cluster/${node}/configuration/wmodules/wmodules`, + {}, + ); + const vdConfiguration = + responseNodeWmodules.data.data?.affected_items?.[0]?.wmodules?.find( + ({ ['vulnerability-detection']: wmodule }) => wmodule, + ); + if (vdConfiguration?.['vulnerability-detection']?.enabled === 'yes') { + return true; + } + } + return false; +} + +async function checkVDIsEnabledManager() { + const responseWmodules = await WzRequest.apiReq( + 'GET', + `/manager/configuration/wmodules/wmodules`, + {}, + ); + + const vdConfiguration = + responseWmodules.data.data?.affected_items?.[0]?.wmodules?.find( + ({ ['vulnerability-detection']: wmodule }) => wmodule, + ); + return vdConfiguration?.['vulnerability-detection']?.enabled === 'yes'; +} + +export const ModuleEnabledCheck = () => { + const [data, setData] = useState<{ enabled: boolean } | null>(null); + const [userPermissionRequirements] = useUserPermissionsRequirements([ + { action: 'cluster:status', resource: '*:*:*' }, + { action: 'cluster:read', resource: 'node:id:*' }, + { action: 'manager:read', resource: '*:*:*' }, + ]); + + const checkVDIsEnabled = async () => { + try { + // Check cluster status + setData(null); + const clusterStatus = await clusterReq(); + + // Check if the module is enabled + const enabled = + clusterStatus.data.data.enabled === 'yes' && + clusterStatus.data.data.running === 'yes' + ? await checkVDIsEnabledCluster() + : await checkVDIsEnabledManager(); + setData({ enabled }); + } catch (error) { + const options = { + context: `${ModuleEnabledCheck.name}.useEffect`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + error: { + error: error, + message: error.message || error, + title: 'Error checking if the module is enabled', + }, + }; + getErrorOrchestrator().handleError(options); + } + }; + + useEffect(() => { + /* Only check if the module is enabled if the user has the expected permissions to + do the API requests */ + if (!userPermissionRequirements) { + checkVDIsEnabled(); + } + }, [userPermissionRequirements]); + + return data?.enabled === false ? ( + +

+ Vulnerabilies detection module is not enabled. You can learn to how to + configure following the{' '} + + documentation + + . +

+
+ ) : null; +}; diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx new file mode 100644 index 0000000000..c021ace475 --- /dev/null +++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -0,0 +1,169 @@ +import React from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { withGuardAsync, withReduxProvider } from '../../../../common/hocs'; +import { + getAngularModule, + getCore, + getSavedObjects, +} from '../../../../../kibana-services'; +import { SavedObject } from '../../../../../react-services'; +import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../../../../../common/constants'; +import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { vulnerabilityDetection } from '../../../../../utils/applications'; +import { LoadingSpinnerDataSource } from '../../../../common/loading/loading-spinner-data-source'; + +const INDEX_PATTERN_CREATION_NO_INDEX = 'INDEX_PATTERN_CREATION_NO_INDEX'; + +async function checkExistenceIndexPattern(indexPatternID: string) { + const response = await getSavedObjects().client.get( + 'index-pattern', + indexPatternID, + ); + + return response?.error?.statusCode !== 404; +} + +async function checkExistenceIndices(indexPatternTitle: string) { + try { + const fields = await SavedObject.getIndicesFields(indexPatternTitle); + return { exist: true, fields }; + } catch (error) { + return { exist: false }; + } +} + +async function createIndexPattern(indexPattern, fields: any) { + try { + await SavedObject.createSavedObject( + 'index-pattern', + indexPattern, + { + attributes: { + title: indexPattern, + timeFieldName: NOT_TIME_FIELD_NAME_INDEX_PATTERN, + }, + }, + fields, + ); + await SavedObject.validateIndexPatternSavedObjectCanBeFound([indexPattern]); + } catch (error) { + return { error: error.message }; + } +} + +export async function validateVulnerabilitiesStateDataSources({ + vulnerabilitiesStatesindexPatternID: indexPatternID, +}) { + try { + // Check the existence of related index pattern + const existIndexPattern = await checkExistenceIndexPattern(indexPatternID); + + // If the idnex pattern does not exist, then check the existence of index + if (!existIndexPattern) { + // Check the existence of indices + const { exist, fields } = await checkExistenceIndices(indexPatternID); + + if (!exist) { + return { + ok: true, + data: { + error: { + title: + 'Vulnerability detection seems to be disabled or has a problem', + type: INDEX_PATTERN_CREATION_NO_INDEX, + }, + }, + }; + } + // If the some index match the index pattern, then create the index pattern + const resultCreateIndexPattern = await createIndexPattern( + indexPatternID, + fields, + ); + if (resultCreateIndexPattern?.error) { + return { + ok: true, + data: { + error: { + title: 'There was a problem creating the index pattern', + message: resultCreateIndexPattern?.error, + }, + }, + }; + } + /* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should + redirects to the Dashboard tab. We want to redirect to this view, because we need the + component is visible (visualizations) to ensure the process that defines the filters for the + Events tab is run when the Dashboard component is unmounted. This workaround solves a + problem in the Events tabs related there are no implicit filters when accessing if the HOC + that protect the view is passed. + */ + getCore().application.navigateToApp(vulnerabilityDetection.id); + } + return { + ok: false, + data: {}, + }; + } catch (error) { + return { + ok: true, + data: { + error: { title: 'There was a problem', message: error.message }, + }, + }; + } +} + +const errorPromptBody = { + INDEX_PATTERN_CREATION_NO_INDEX: ( +

+ Please check the cluster status. Also, you can check the{' '} + + vulnerability detection documentation. + +

+ ), +}; + +export const PromptCheckIndex = props => { + const { refresh } = props; + const { title, message } = props?.error; + const body = errorPromptBody?.[props?.error?.type] ||

{message}

; + + return ( + {title}} + body={body} + actions={ + + Refresh + + } + /> + ); +}; + +const mapStateToProps = state => ({ + vulnerabilitiesStatesindexPatternID: + state.appConfig.data['vulnerabilities.pattern'], +}); + +export const withVulnerabilitiesStateDataSource = compose( + withReduxProvider, + connect(mapStateToProps), + withGuardAsync( + validateVulnerabilitiesStateDataSources, + ({ error, check }) => , + () => , + ), +); diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx index 3a3ce9aa7a..c8ba68c25f 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx @@ -1,14 +1,4 @@ import { useState, useEffect } from 'react'; -import { search } from '../../../../common/search-bar/search-bar-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 { @@ -19,17 +9,10 @@ interface UseCheckIndexFieldsResult { resultIndexData: any; } -const useCheckIndexFields = ( - indexPatternId: string, - indexPattern: IIndexPattern | undefined, - indexType: string, - filters?: Filter[], - query?: any, -) => { +const useCheckIndexFields = (indexPatternId: string, indexType: string) => { 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(() => { @@ -39,26 +22,7 @@ const useCheckIndexFields = ( // 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); - }); + setIsLoading(false); } catch (error) { setError(error); setIsError(true); @@ -69,13 +33,12 @@ const useCheckIndexFields = ( checkIndexFields(); } - }, [indexPatternId, query, indexPattern]); + }, [indexPatternId, indexType]); return { isError, error, isSuccess, - resultIndexData, isLoading, } as UseCheckIndexFieldsResult; }; diff --git a/plugins/main/public/components/overview/vulnerabilities/common/vulnerability_detector_adapters.tsx b/plugins/main/public/components/overview/vulnerabilities/common/vulnerability_detector_adapters.tsx new file mode 100644 index 0000000000..b605aff9c4 --- /dev/null +++ b/plugins/main/public/components/overview/vulnerabilities/common/vulnerability_detector_adapters.tsx @@ -0,0 +1,279 @@ +import { + AUTHORIZED_AGENTS, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER, + WAZUH_ALERTS_PATTERN, +} from '../../../../../common/constants'; +import { AppState } from '../../../../react-services'; +import { FilterHandler } from '../../../../utils/filter-handler'; +import { Filter } from '../../../../../../../src/plugins/data/public'; +import { FilterManager } from '../../../../../../../src/plugins/data/public'; +import { FilterStateStore } from '../../../../../../../src/plugins/data/common'; + +const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters'; +const SESSION_STORAGE_PREV_FILTER_NAME = 'wazuh_persistent_current_filter'; + +/** + * When the component is unmounted, the original filters that arrived + * when the component was mounted are added. + * Both when the component is mounted and unmounted, the index pattern is + * updated so that the pin action adds the agent with the correct index pattern. + */ +export const vulnerabilityIndexFiltersAdapter = ( + filterManager: FilterManager, + defaultIndexPatternID: string, +) => { + const filterHandler = new FilterHandler(AppState.getCurrentPattern()); + const initialFilters: Filter[] = []; + const storagePreviousFilters = sessionStorage.getItem( + SESSION_STORAGE_FILTERS_NAME, + ); + if (storagePreviousFilters) { + const previousFilters = JSON.parse(storagePreviousFilters); + const previousFiltersWithoutImplicitFilters = previousFilters.filter( + (filter: Filter) => !filter?.$state?.isImplicit, + ); + previousFiltersWithoutImplicitFilters.forEach((filter: Filter) => { + initialFilters.push(filter); + }); + } + + /** + Add vulnerability module implicit filters + */ + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + true, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + ); + + // rule.groups is added so that the events tab can use it + const ruleGroupsFilter = filterHandler.ruleGroupQuery( + 'vulnerability-detector', + ); + initialFilters.unshift(ruleGroupsFilter); + initialFilters.unshift(managerFilter); + const vulnerabilitiesFilters = initialFilters.map((filter: Filter) => { + return { + ...filter, + meta: { + ...filter.meta, + index: defaultIndexPatternID, + }, + }; + }); + sessionStorage.setItem( + SESSION_STORAGE_FILTERS_NAME, + JSON.stringify(initialFilters), + ); + /* The rule.groups filter is removed. It is not applicable to the case of the vulnerabilities module since it has its own index */ + const vulnerabilitiesFiltersWithoutRuleGroup = vulnerabilitiesFilters.filter( + (filter: Filter) => filter.meta.key !== 'rule.groups', + ); + const implicitPinnedAgent = getImplicitPinnedAgent( + initialFilters, + defaultIndexPatternID, + ); + if (implicitPinnedAgent) { + const filtersWithoutNormalAgents = + vulnerabilitiesFiltersWithoutRuleGroup.filter(x => { + return x.meta.key !== 'agent.id'; + }); + filtersWithoutNormalAgents.push(implicitPinnedAgent); + filterManager.setFilters(filtersWithoutNormalAgents); + } else { + filterManager.setFilters(vulnerabilitiesFiltersWithoutRuleGroup); + } +}; + +export const restorePrevIndexFiltersAdapter = ( + previousFilters: Filter[], + toIndexPattern: string | null, + filterManager: FilterManager, + defaultIndexPatternID: string, +) => { + const filterHandler = new FilterHandler(AppState.getCurrentPattern()); + const isCluster = AppState.getClusterInfo().status == 'enabled'; + const cleanedFilters = cleanFilters( + previousFilters, + defaultIndexPatternID, + ).filter( + (filter: Filter) => + filter?.meta?.key !== + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + ); + /* Restore original manager implicit filter */ + const managerFilter = filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + isCluster, + ); + cleanedFilters.unshift(managerFilter); + const implicitPinnedAgent = getImplicitPinnedAgent( + previousFilters, + toIndexPattern ?? WAZUH_ALERTS_PATTERN, + ); + if (implicitPinnedAgent) { + const cleanedFiltersWithoutNormalAgents = cleanedFilters.filter(x => { + return x.meta.key !== 'agent.id'; + }); + cleanedFiltersWithoutNormalAgents.push(implicitPinnedAgent); + filterManager.setFilters(cleanedFiltersWithoutNormalAgents); + } else { + filterManager.setFilters(cleanedFilters); + } +}; + +export const onUpdateAdapter = ( + filters: Filter[], + filterManager: FilterManager, + onFiltersUpdated?: (filters: Filter[]) => void, +) => { + const prevStoragePattern = sessionStorage.getItem( + SESSION_STORAGE_PREV_FILTER_NAME, + ); + 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 + */ + const cleanedFilters = cleanFilters( + filters, + prevStoragePattern ?? WAZUH_ALERTS_PATTERN, + ); + if (storagePreviousFilters) { + const previousFilters = JSON.parse(storagePreviousFilters); + const previousImplicitFilters = previousFilters.filter( + (filter: Filter) => filter?.$state?.isImplicit, + ); + const cleanedPreviousImplicitFilters = cleanFilters( + previousImplicitFilters, + prevStoragePattern ?? WAZUH_ALERTS_PATTERN, + ); + /* Normal filters added to storagePreviousFilters are added to keep them between dashboard and inventory tab */ + const newFilters = filters.filter( + (filter: Filter) => !filter?.$state?.isImplicit, + ); + + sessionStorage.setItem( + SESSION_STORAGE_FILTERS_NAME, + JSON.stringify([...previousImplicitFilters, ...newFilters]), + ); + + filterManager.setFilters([ + ...cleanedPreviousImplicitFilters, + ...cleanedFilters, + ]); + onFiltersUpdated && + onFiltersUpdated([...cleanedPreviousImplicitFilters, ...cleanedFilters]); + } else { + filterManager.setFilters(cleanedFilters); + onFiltersUpdated && onFiltersUpdated(cleanedFilters); + } +}; + +/** + * Verify if a pinned agent exists, identifying it by its meta.isImplicit attribute or by the agentId query param URL. + * We also compare the agent.id filter with the agentId query param because the OSD filter definition does not include the "isImplicit" attribute that Wazuh adds. + * There may be cases where the "isImplicit" attribute is lost, since any action regarding filters that is done with the + * filterManager ( addFilters, setFilters, setGlobalFilters, setAppFilters) + * does a mapAndFlattenFilters mapping to the filters that removes any attributes that are not part of the filter definition. + * */ +const getImplicitPinnedAgent = ( + filters: Filter[], + toIndexPattern: string, +): Filter | undefined => { + const pinnedAgentByFilterManager = filters.find( + (filter: Filter) => + filter?.meta?.key === 'agent.id' && !!filter?.$state?.isImplicit, + ); + const url = window.location.href; + const regex = new RegExp('agentId=' + '[^&]*'); + const match = url.match(regex); + const isPinnedAgentByUrl = match && match[0]; + if (pinnedAgentByFilterManager && isPinnedAgentByUrl) { + return { + ...pinnedAgentByFilterManager, + meta: { + ...pinnedAgentByFilterManager.meta, + index: toIndexPattern, + }, + $state: { store: FilterStateStore.APP_STATE, isImplicit: true }, + }; + } + + if (isPinnedAgentByUrl) { + const agentId = match[0].split('=')[1]; + return { + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentId }, + type: 'phrase', + index: toIndexPattern, + }, + query: { + match: { + 'agent.id': { + query: agentId, + type: 'phrase', + }, + }, + }, + $state: { store: 'appState', isImplicit: true }, + }; + } + return undefined; +}; + +/** + * 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. + * This cleanup adjusts the index pattern of a pinned agent, if applicable. + * @param previousFilters + * @returns + */ +const cleanFilters = ( + previousFilters: Filter[], + indexPatternToClean: string, +) => { + const mappedFilters = previousFilters.filter( + (filter: Filter) => + filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters + filter?.meta?.index !== indexPatternToClean, + ); + return mappedFilters; +}; + +export const updateFiltersStorage = (filters: Filter[]) => { + const storagePreviousFilters = sessionStorage.getItem( + SESSION_STORAGE_FILTERS_NAME, + ); + if (storagePreviousFilters) { + const previousFilters = JSON.parse(storagePreviousFilters); + const previousImplicitFilters = previousFilters.filter( + (filter: Filter) => filter?.$state?.isImplicit, + ); + /* Normal filters added to storagePreviousFilters are added to keep them between dashboard and inventory tab */ + const newFilters = filters.filter( + (filter: Filter) => !filter?.$state?.isImplicit, + ); + + sessionStorage.setItem( + SESSION_STORAGE_FILTERS_NAME, + JSON.stringify([...previousImplicitFilters, ...newFilters]), + ); + } +}; 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 1fd7a8f421..9174ced121 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -37,8 +37,15 @@ import { useDocViewer } from '../../../../common/doc-viewer/use-doc-viewer'; import { withErrorBoundary } from '../../../../common/hocs'; import { search } from '../../../../common/search-bar/search-bar-service'; import { exportSearchToCSV } from '../../../../common/data-grid/data-grid-service'; -import { WAZUH_INDEX_TYPE_VULNERABILITIES } from '../../../../../../common/constants'; -import useCheckIndexFields from '../../common/hooks/useCheckIndexFields'; +import { + vulnerabilityIndexFiltersAdapter, + onUpdateAdapter, + restorePrevIndexFiltersAdapter, +} from '../../common/vulnerability_detector_adapters'; +import { compose } from 'redux'; +import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; +import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; +import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; const InventoryVulsComponent = () => { const appConfig = useAppConfig(); @@ -46,7 +53,15 @@ const InventoryVulsComponent = () => { appConfig.data['vulnerabilities.pattern']; const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + onMount: vulnerabilityIndexFiltersAdapter, + onUpdate: onUpdateAdapter, + onUnMount: restorePrevIndexFiltersAdapter, }); + + const fetchFilters = DataSourceFilterManagerVulnerabilitiesStates.getFilters( + searchBarProps.filters, + VULNERABILITIES_INDEX_PATTERN_ID, + ); const { isLoading, filters, query, indexPatterns } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); @@ -97,26 +112,12 @@ 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 && isSuccess) { + if (!isLoading) { setIndexPattern(indexPatterns?.[0] as IndexPattern); search({ indexPattern: indexPatterns?.[0] as IndexPattern, - filters, + filters: fetchFilters, query, pagination, sorting, @@ -138,13 +139,13 @@ const InventoryVulsComponent = () => { JSON.stringify(searchBarProps), JSON.stringify(pagination), JSON.stringify(sorting), - isLoadingCheckIndex, + JSON.stringify(fetchFilters), ]); const onClickExportResults = async () => { const params = { indexPattern: indexPatterns?.[0] as IndexPattern, - filters, + filters: fetchFilters, query, fields: columnVisibility.visibleColumns, pagination: { @@ -169,100 +170,97 @@ const InventoryVulsComponent = () => { 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

-
-
- - - - - - - -
- )} - -
+ <> + + + <> + {isLoading ? ( + + ) : ( + + )} + {isSearching ? : null} + {!isLoading && !isSearching && results?.hits?.total === 0 ? ( + + ) : null} + {!isLoading && !isSearching && 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 InventoryVuls = withErrorBoundary(InventoryVulsComponent); +export const InventoryVuls = compose( + withErrorBoundary, + withVulnerabilitiesStateDataSource, +)(InventoryVulsComponent); 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 c29bc6393b..6508987edb 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { SearchResponse } from '../../../../../../../../src/core/server'; import { getPlugins } from '../../../../../kibana-services'; import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { getDashboardPanels } from './dashboard_panels'; @@ -10,9 +11,26 @@ 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'; +import { + vulnerabilityIndexFiltersAdapter, + restorePrevIndexFiltersAdapter, + onUpdateAdapter, + updateFiltersStorage, +} from '../../common/vulnerability_detector_adapters'; +import { search } from '../../../../common/search-bar/search-bar-service'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { + ErrorFactory, + ErrorHandler, + HttpError, +} from '../../../../../react-services/error-management'; +import { compose } from 'redux'; +import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; +import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; +import { DataSourceFilterManagerVulnerabilitiesStates } from '../../../../../react-services/data-sources'; +import { DashboardContainerInput } from '../../../../../../../../src/plugins/dashboard/public'; + const plugins = getPlugins(); const SearchBar = getPlugins().data.ui.SearchBar; @@ -29,29 +47,58 @@ const DashboardVulsComponent: React.FC = () => { appConfig.data['vulnerabilities.pattern']; const { searchBarProps } = useSearchBar({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, + onMount: vulnerabilityIndexFiltersAdapter, + onUpdate: onUpdateAdapter, + onUnMount: restorePrevIndexFiltersAdapter, }); - 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, - ); + /* This function is responsible for updating the storage filters so that the + filters between dashboard and inventory added using visualizations call to actions. + Without this feature, filters added using visualizations call to actions are + not maintained between dashboard and inventory tabs */ + const handleFilterByVisualization = (newInput: DashboardContainerInput) => { + updateFiltersStorage(newInput.filters); + }; + + const fetchFilters = DataSourceFilterManagerVulnerabilitiesStates.getFilters( + searchBarProps.filters, + VULNERABILITIES_INDEX_PATTERN_ID, + ); + + const { isLoading, query, indexPatterns } = searchBarProps; + + const [isSearching, setIsSearching] = useState(false); + const [results, setResults] = useState({} as SearchResponse); + + useEffect(() => { + if (!isLoading) { + search({ + indexPattern: indexPatterns?.[0] as IndexPattern, + filters: fetchFilters, + query, + }) + .then(results => { + setResults(results); + setIsSearching(false); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + ErrorHandler.handleError(searchError); + setIsSearching(false); + }); + } + }, [JSON.stringify(searchBarProps), JSON.stringify(fetchFilters)]); return ( <> <> - {isLoading || isLoadingSearchbar ? : null} - {!isLoading && !isLoadingSearchbar ? ( + + {isLoading ? : null} + {!isLoading ? ( { showQueryBar={true} /> ) : null} - {!isLoadingSearchbar && - !isLoading && - (isError || - !resultIndexData || - resultIndexData?.hits?.total === 0) ? ( - + {isSearching ? : null} + {!isLoading && !isSearching && results?.hits?.total === 0 ? ( + ) : null} - {!isLoadingSearchbar && - !isLoading && - isSuccess && - resultIndexData && - resultIndexData?.hits?.total !== 0 ? ( + {!isLoading && !isSearching && results?.hits?.total > 0 ? (
{ VULNERABILITIES_INDEX_PATTERN_ID, ), isFullScreenMode: false, - filters: searchBarProps.filters ?? [], + filters: fetchFilters ?? [], useMargins: false, id: 'vulnerability-detector-dashboard-tab-filters', timeRange: { @@ -98,6 +138,7 @@ const DashboardVulsComponent: React.FC = () => { }, hidePanelTitles: true, }} + onInputUpdated={handleFilterByVisualization} />
{ viewMode: ViewMode.VIEW, panels: getKPIsPanel(VULNERABILITIES_INDEX_PATTERN_ID), isFullScreenMode: false, - filters: searchBarProps.filters ?? [], + filters: fetchFilters ?? [], useMargins: true, id: 'kpis-vulnerability-detector-dashboard-tab', timeRange: { @@ -121,13 +162,14 @@ const DashboardVulsComponent: React.FC = () => { }, hidePanelTitles: true, }} + onInputUpdated={handleFilterByVisualization} /> { }, hidePanelTitles: false, }} + onInputUpdated={handleFilterByVisualization} />
) : null} @@ -152,4 +195,7 @@ const DashboardVulsComponent: React.FC = () => { ); }; -export const DashboardVuls = withErrorBoundary(DashboardVulsComponent); +export const DashboardVuls = compose( + withErrorBoundary, + withVulnerabilitiesStateDataSource, +)(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 3269a8d65b..b7c98727e1 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 @@ -245,7 +245,7 @@ const getVisStateTopVulnerabilitiesEndpoints = (indexPatternId: string) => { enabled: true, type: 'terms', params: { - field: 'agent.id', + field: 'agent.name', orderBy: '1', order: 'desc', size: 10, @@ -253,7 +253,7 @@ const getVisStateTopVulnerabilitiesEndpoints = (indexPatternId: string) => { otherBucketLabel: 'Other', missingBucket: false, missingBucketLabel: 'Missing', - customLabel: 'package.path', + customLabel: 'Endpoint name', }, schema: 'segment', }, 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 7d8e2c64be..6f3c07cb35 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 @@ -6,7 +6,7 @@ const getVisStateFilter = ( indexPatternId: string, title: string, label: string, - fieldName: string + fieldName: string, ) => { return { id, @@ -79,9 +79,11 @@ const getVisStateFilter = ( }; export const getDashboardFilters = ( - indexPatternId: string + indexPatternId: string, ): { - [panelId: string]: DashboardPanelState; + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; } => { return { topPackageSelector: { @@ -100,7 +102,7 @@ export const getDashboardFilters = ( indexPatternId, 'Top packages vulnerabilities', 'Top 5 packages', - 'package.name' + 'package.name', ), }, }, @@ -120,7 +122,7 @@ export const getDashboardFilters = ( indexPatternId, 'Top operating system vulnerabilities', 'Top 5 OS', - 'host.os.full' + 'host.os.full', ), }, }, @@ -140,7 +142,7 @@ export const getDashboardFilters = ( indexPatternId, 'Agent filter', 'Top 5 agents', - 'agent.id' + 'agent.name', ), }, }, @@ -160,7 +162,7 @@ export const getDashboardFilters = ( indexPatternId, 'Top vulnerabilities', 'Top 5 vulnerabilities', - 'vulnerability.id' + 'vulnerability.id', ), }, }, 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 3eeb3c0d62..9a2f166307 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 @@ -74,6 +74,10 @@ class WzAgentSelector extends Component { const agentFilters = currentAppliedFilters.filter(x => { return x.meta.key !== 'agent.id'; }); + const cookieCurrentPattern = + AppState.getCurrentPattern() || getSettingDefaultValue('pattern'); + const currentPattern = + this.props?.moduleIndexPatternTitle ?? cookieCurrentPattern; const filter = { meta: { alias: null, @@ -82,8 +86,7 @@ class WzAgentSelector extends Component { negate: false, params: { query: agentIdList[0] }, type: 'phrase', - index: - AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), + index: currentPattern, }, query: { match: { diff --git a/plugins/main/public/controllers/management/components/management/common/resources-handler.ts b/plugins/main/public/controllers/management/components/management/common/resources-handler.ts index de4583b4f0..f0b57c0625 100644 --- a/plugins/main/public/controllers/management/components/management/common/resources-handler.ts +++ b/plugins/main/public/controllers/management/components/management/common/resources-handler.ts @@ -93,7 +93,7 @@ export class ResourcesHandler { fileName: string, content: string, overwrite: boolean, - relativeDirname: string, + relativeDirname?: string, ) { try { const result = await WzRequest.apiReq( @@ -102,7 +102,9 @@ export class ResourcesHandler { { params: { overwrite: overwrite, - relative_dirname: relativeDirname, + ...(this.resource !== 'lists' + ? { relative_dirname: relativeDirname } + : {}), }, body: content.toString(), origin: 'raw', @@ -119,7 +121,7 @@ export class ResourcesHandler { * @param {Resource} resource * @param {String} fileName */ - async deleteFile(fileName: string, relativeDirname: string = '') { + async deleteFile(fileName: string, relativeDirname?: string) { try { const result = await WzRequest.apiReq( 'DELETE', diff --git a/plugins/main/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap b/plugins/main/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap index 624404e142..6f6d02f5a1 100644 --- a/plugins/main/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap +++ b/plugins/main/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap @@ -34,12 +34,13 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` - - - +

@@ -67,12 +68,13 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` - - - +

@@ -107,7 +109,7 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` class="euiLink euiLink--primary statWithLink" href="" rel="noreferrer" - style="font-weight: normal;" + style="font-weight: normal; color: rgb(0, 120, 113);" > - diff --git a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx index f493d8535b..950e659e15 100644 --- a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx +++ b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx @@ -46,7 +46,10 @@ export function LastAlertsStat() { {countLastAlerts ?? '-'} diff --git a/plugins/main/public/controllers/overview/components/stats.js b/plugins/main/public/controllers/overview/components/stats.js index a14758e3f3..6701993a02 100644 --- a/plugins/main/public/controllers/overview/components/stats.js +++ b/plugins/main/public/controllers/overview/components/stats.js @@ -18,6 +18,7 @@ import { EuiFlexGroup, EuiPage, EuiToolTip, + EuiLink, } from '@elastic/eui'; import { withErrorBoundary } from '../../../components/common/hocs'; import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; @@ -56,7 +57,7 @@ export const Stats = withErrorBoundary( sessionStorage.removeItem('wz-agents-overview-table-filter'); } getCore().application.navigateToApp(endpointSummary.id, { - path: '#/agents-preview', + path: `#${endpointSummary.redirectTo()}`, }); } @@ -81,15 +82,15 @@ export const Stats = withErrorBoundary( position='top' content={`Go to ${label.toLowerCase()} agents`} > - {typeof this.props[status] !== 'undefined' ? this.props[status] : '-'} - + } description={`${label} agents`} diff --git a/plugins/main/public/plugin.ts b/plugins/main/public/plugin.ts index 313a2edbc3..1873b9b69b 100644 --- a/plugins/main/public/plugin.ts +++ b/plugins/main/public/plugin.ts @@ -61,6 +61,10 @@ export class WazuhPlugin core: CoreSetup, plugins: WazuhSetupPlugins, ): Promise { + // Hide the discover deprecation notice + // After opensearch version 2.11.0 this line may be deleted + localStorage.setItem('discover:deprecation-notice:show', 'false'); + // Get custom logos configuration to start up the app with the correct logos let logosInitialState = {}; try { diff --git a/plugins/main/public/react-services/data-sources/index.ts b/plugins/main/public/react-services/data-sources/index.ts new file mode 100644 index 0000000000..88e23ab274 --- /dev/null +++ b/plugins/main/public/react-services/data-sources/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './vulnerabilities-states'; diff --git a/plugins/main/public/react-services/data-sources/types.ts b/plugins/main/public/react-services/data-sources/types.ts new file mode 100644 index 0000000000..e5c1b13c2b --- /dev/null +++ b/plugins/main/public/react-services/data-sources/types.ts @@ -0,0 +1,3 @@ +export interface IDataSourcesFilterManager { + getFilters(searchBarFilters: any[], indexPatternTitle: string): any[]; +} diff --git a/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts b/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts new file mode 100644 index 0000000000..24b6585473 --- /dev/null +++ b/plugins/main/public/react-services/data-sources/vulnerabilities-states.ts @@ -0,0 +1,95 @@ +import { + AUTHORIZED_AGENTS, + DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, +} from '../../../common/constants'; +import store from '../../redux/store'; +import { IDataSourcesFilterManager } from './types'; + +/** + * Get the filter that excludes the data related to Wazuh servers + * @param indexPatternTitle Index pattern title + * @returns + */ +function getFilterExcludeManager(indexPatternTitle: string) { + return { + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: true, + params: { query: '000' }, + type: 'phrase', + index: indexPatternTitle, + controlledBy: DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, + }, + query: { match_phrase: { 'agent.id': '000' } }, + $state: { store: 'appState' }, + }; +} + +/** + * Get the filter that restrict the search to the allowed agents + * @param agentsIds + * @param indexPatternTitle + * @returns + */ +function getFilterAllowedAgents( + agentsIds: string[], + indexPatternTitle: string, +) { + const field = 'agent.id'; + return { + meta: { + index: indexPatternTitle, + type: 'phrases', + key: field, + value: agentsIds.toString(), + params: agentsIds, + alias: null, + negate: false, + disabled: false, + controlledBy: AUTHORIZED_AGENTS, + }, + query: { + bool: { + should: agentsIds.map(id => { + return { + match_phrase: { + [field]: id, + }, + }; + }), + minimum_should_match: 1, + }, + }, + $state: { + store: 'appState', + }, + }; +} + +export const DataSourceFilterManagerVulnerabilitiesStates: IDataSourcesFilterManager = + { + getFilters(searchBarFilters: any[], indexPatternTitle: string) { + return [ + ...searchBarFilters, + /* Add the filter to exclude the data related to servers (managers) due to + the setting hideManagerAlerts is enabled */ + ...(store.getState().appConfig?.data?.hideManagerAlerts && + indexPatternTitle + ? [getFilterExcludeManager(indexPatternTitle)] + : []), + /* Add the allowed agents related to the user permissions to read data from agents in the + API server */ + ...(store.getState().appStateReducers?.allowedAgents?.length > 0 && + indexPatternTitle + ? [ + getFilterAllowedAgents( + store.getState().appStateReducers?.allowedAgents, + indexPatternTitle, + ), + ] + : []), + ]; + }, + }; diff --git a/plugins/main/public/react-services/vis-factory-handler.js b/plugins/main/public/react-services/vis-factory-handler.js index cfcde9962a..9adbde6958 100644 --- a/plugins/main/public/react-services/vis-factory-handler.js +++ b/plugins/main/public/react-services/vis-factory-handler.js @@ -58,9 +58,13 @@ export class VisFactoryHandler { * @param {*} subtab * @param {*} localChange */ - static async buildOverviewVisualizations(filterHandler, tab, subtab, fromDiscover = false) { + static async buildOverviewVisualizations( + filterHandler, + tab, + subtab, + fromDiscover = false, + ) { const rawVisualizations = new RawVisualizations(); - //if(rawVisualizations.getType() !== 'general'){ rawVisualizations.setType('general'); const $injector = getAngularModule().$injector; const commonData = $injector.get('commonData'); @@ -68,18 +72,20 @@ export class VisFactoryHandler { try { const currentPattern = AppState.getCurrentPattern(); // TODO change logic to read common/modules/module-defaults.js configuration - const data = - !['sca', 'vuls'].includes(tab) - ? await GenericRequest.request( + const data = !['sca', 'vuls'].includes(tab) + ? await GenericRequest.request( 'GET', - `/elastic/visualizations/overview-${tab}/${currentPattern}` + `/elastic/visualizations/overview-${tab}/${currentPattern}`, ) - : false; + : false; data && rawVisualizations.assignItems(data.data.raw); - if (!fromDiscover) { + /* For the vuls component only, it is not necessary to call the assignFilters method since it is handled by the same module due to its particular characteristics. Only the condition for vuls is added so as not to alter the rest. This functionality should be applied in a higher hierarchy in the future. */ + if (tab !== 'vuls' && !fromDiscover) { commonData.assignFilters(filterHandler, tab); } - store.dispatch(updateVis({ update: true, raw: rawVisualizations.getList() })); + store.dispatch( + updateVis({ update: true, raw: rawVisualizations.getList() }), + ); } catch (error) { throw error; } @@ -93,7 +99,13 @@ export class VisFactoryHandler { * @param {*} localChange * @param {*} id */ - static async buildAgentsVisualizations(filterHandler, tab, subtab, id, fromDiscover = false) { + static async buildAgentsVisualizations( + filterHandler, + tab, + subtab, + id, + fromDiscover = false, + ) { const rawVisualizations = new RawVisualizations(); // if (rawVisualizations.getType() !== 'agents') { rawVisualizations.setType('agents'); @@ -102,15 +114,15 @@ export class VisFactoryHandler { try { // TODO change logic to read common/modules/module-defaults.js configuration - const data = - (!['sca', 'office', 'vuls'].includes(tab)) - ? await GenericRequest.request( + const data = !['sca', 'office', 'vuls'].includes(tab) + ? await GenericRequest.request( 'GET', - `/elastic/visualizations/agents-${tab}/${AppState.getCurrentPattern()}` + `/elastic/visualizations/agents-${tab}/${AppState.getCurrentPattern()}`, ) - : false; + : false; data && rawVisualizations.assignItems(data.data.raw); - if (!fromDiscover) { + /* For the vuls component only, it is not necessary to call the assignFilters method since it is handled by the same module due to its particular characteristics. Only the condition for vuls is added so as not to alter the rest. This functionality should be applied in a higher hierarchy in the future. */ + if (tab !== 'vuls' && !fromDiscover) { commonData.assignFilters(filterHandler, tab, id); } store.dispatch(updateVis({ update: true })); diff --git a/plugins/main/public/services/common-data.js b/plugins/main/public/services/common-data.js index 285285be5a..bd5d18d0f4 100644 --- a/plugins/main/public/services/common-data.js +++ b/plugins/main/public/services/common-data.js @@ -14,6 +14,7 @@ import { GenericRequest } from '../react-services/generic-request'; import { ShareAgent } from '../factories/share-agent'; import { ModulesHelper } from '../components/common/modules/modules-helper'; import rison from 'rison-node'; +import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../common/constants'; export class CommonData { /** @@ -158,6 +159,11 @@ export class CommonData { ? AppState.getClusterInfo().cluster : AppState.getClusterInfo().manager, isCluster, + tab === 'vuls' + ? VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ] + : undefined, ), ); if (tab !== 'general' && tab !== 'welcome') { diff --git a/plugins/main/public/utils/filter-handler.js b/plugins/main/public/utils/filter-handler.js index 29c7e210af..e6ffbad168 100644 --- a/plugins/main/public/utils/filter-handler.js +++ b/plugins/main/public/utils/filter-handler.js @@ -96,25 +96,29 @@ export class FilterHandler { }; return result; } - - managerQuery(manager, isCluster) { + /** + * This function takes two parameters, the isCluster parameter is a boolean thats defines if it uses cluster.name key or manager.name + * @param {*} manager + * @param {*} isCluster + * @param {*} fixedKey + * @returns + */ + managerQuery(manager, isCluster, fixedKey = undefined) { + const metaKey = fixedKey + ? fixedKey + : isCluster + ? 'cluster.name' + : 'manager.name'; const result = this.base(); - result.meta.key = isCluster ? 'cluster.name' : 'manager.name'; + result.meta.key = metaKey; result.meta.value = manager; result.meta.params.query = manager; - result.query.match = isCluster - ? { - 'cluster.name': { - query: manager, - type: 'phrase', - }, - } - : { - 'manager.name': { - query: manager, - type: 'phrase', - }, - }; + result.query.match = { + [metaKey]: { + query: manager, + type: 'phrase', + }, + }; return result; } diff --git a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json index 5a4d7878de..56beefca4b 100644 --- a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json +++ b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json @@ -94,16 +94,6 @@ "title": "Set time filter to 24h", "subTitle": "Change the default value of the Kibana timeFilter configuration", "label": "checks.timeFilter" - }, - { - "title": "Vulnerabilities index pattern", - "subTitle": "Enable or disable the vulnerabilities index pattern health check when opening the app.", - "label": "checks.vulnerabilities.pattern" - }, - { - "title": "Fim index pattern", - "subTitle": "Enable or disable the fim index pattern health check when opening the app.", - "label": "checks.fim.pattern" } ] }, @@ -228,4 +218,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index 231a5a3a56..4d3e0c09bd 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -806,60 +806,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, - 'checks.vulnerabilities.pattern': { - title: 'Vulnerabilities index pattern', - description: - 'Enable or disable the vulnerabilities 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(); - }, - }, - '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 index bb43bc9ab4..a68baf66e8 100644 --- a/scripts/vulnerabilities-events-injector/DIS_Template.json +++ b/scripts/vulnerabilities-events-injector/DIS_Template.json @@ -1,348 +1,273 @@ { - "mappings": { - "date_detection": false, - "dynamic_templates": [ - { - "strings_as_keyword": { - "mapping": { - "ignore_above": 1024, - "type": "keyword" - }, - "match_mapping_type": "string" - } + "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": { - "@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": { + "version": { + "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" + } + } + } + } + }, + "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" + } }, - "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" + }, + "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" - }, - "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" - } - } + } + } + }, + "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" + }, + "wazuh": { + "properties": { + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "manager": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "name": { + "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 index 90347cd08a..aa50604a3a 100644 --- a/scripts/vulnerabilities-events-injector/dataInjectScript.py +++ b/scripts/vulnerabilities-events-injector/dataInjectScript.py @@ -85,7 +85,6 @@ def generateRandomPackage(): 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)) @@ -118,19 +117,23 @@ def generateRandomVulnerability(): vulnerability['severity'] = random.choice(['Low','Medium','High','Critical']) return(vulnerability) +def generateRandomWazuh(): + wazuh = {} + wazuh['cluster'] = {'name':random.choice(['wazuh.manager', 'wazuh']), 'node':random.choice(['master','worker-01','worker-02','worker-03'])} + return(wazuh) + 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(), + 'wazuh':generateRandomWazuh() } def verifyIndex(index,instance): @@ -172,14 +175,34 @@ def verifySettings(): 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") + ip = input("\nEnter the IP of your Indexer [default=0.0.0.0]: \n") + if ip == '': + ip = '0.0.0.0' + + port = input("\nEnter the port of your Indexer [default=9200]: \n") + if port == '': + port = '9200' + + index = input("\nEnter the index name [default=wazuh-states-vulnerabilities]: \n") + if index == '': + index = 'wazuh-states-vulnerabilities' + url = 'https://{}:{}/{}/_doc'.format(ip, port, index) - username = input("\nUsername: \n") - password = input("\nPassword: \n") + + username = input("\nUsername [default=admin]: \n") + if username == '': + username = 'admin' + + password = input("\nPassword [default=admin]: \n") + if password == '': + password = 'admin' + config = {'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") + + store = input("\nDo you want to store these settings for future use? (y/n) [default=n] \n") + if store == '': + store = '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': @@ -206,10 +229,15 @@ def injectEvents(generator): def main(): - action = input("Do you want to inject data or save it to a file? (i/s) \n") + action = input("Do you want to inject data or save it to a file? (i/s) [default=i]\n") + if action == '': + action = 'i' + 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") + number = input("\nHow many events do you want to generate? [default=100]\n") + if number == '': + number = '100' while(not number.isdigit()): number = input("Invalid option.\n How many events do you want to generate? \n") data = generateRandomData(number)