From 5ad714c700a568b258fbd07464cc377630cf79db Mon Sep 17 00:00:00 2001 From: jbiset Date: Wed, 22 Nov 2023 17:15:50 -0300 Subject: [PATCH 1/5] Add vulnerabilities configurations and healthCheck --- plugins/main/common/config-equivalences.js | 10 + plugins/main/common/constants.ts | 562 +++++++++++++++++- plugins/main/common/plugin-settings.test.ts | 18 + .../health-check/components/check-result.tsx | 130 ++-- .../health-check.container.test.tsx.snap | 27 + .../container/health-check.container.test.tsx | 12 + .../container/health-check.container.tsx | 70 ++- .../services/check-pattern-support.service.ts | 96 +-- .../health-check/types/check_logger.ts | 9 +- .../types/result-icons-presets.ts | 90 +-- .../vulnerabilities/common/constants.ts | 1 - .../dashboards/inventory/inventory.tsx | 190 +++--- .../dashboards/overview/dashboard.tsx | 16 +- .../dashboards/overview/dashboard_panels.ts | 24 +- .../overview/dashboard_panels_filters.ts | 13 +- .../overview/dashboard_panels_kpis.ts | 13 +- .../use_search_bar_configuration.tsx | 54 +- .../public/react-services/saved-objects.js | 129 ++-- .../fixtures/configuration.panel.text.json | 25 + plugins/wazuh-core/common/constants.ts | 92 ++- 20 files changed, 1291 insertions(+), 290 deletions(-) delete mode 100644 plugins/main/public/components/overview/vulnerabilities/common/constants.ts diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js index e9c3a01bea..fcfd359454 100644 --- a/plugins/main/common/config-equivalences.js +++ b/plugins/main/common/config-equivalences.js @@ -58,6 +58,8 @@ export const configEquivalences = { '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 = { @@ -94,6 +96,8 @@ export const nameEquivalence = { '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', }; const HEALTH_CHECK = 'Health Check'; @@ -101,6 +105,7 @@ const GENERAL = 'General'; const SECURITY = 'Security'; const MONITORING = 'Monitoring'; const STATISTICS = 'Statistics'; +const VULNERABILITIES = 'Vulnerabilities'; const CUSTOMIZATION = 'Logo Customization'; export const categoriesNames = [ HEALTH_CHECK, @@ -108,6 +113,7 @@ export const categoriesNames = [ SECURITY, MONITORING, STATISTICS, + VULNERABILITIES, CUSTOMIZATION, ]; @@ -145,6 +151,8 @@ export const categoriesEquivalence = { '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'; @@ -216,4 +224,6 @@ export const formEquivalence = { '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 26d3e79ae1..5b690d1e06 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -48,6 +48,9 @@ export const WAZUH_STATISTICS_DEFAULT_STATUS = true; export const WAZUH_STATISTICS_DEFAULT_FREQUENCY = 900; export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; +// Wazuh vulnerabilities +export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; + // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; @@ -402,6 +405,10 @@ export const ELASTIC_NAME = 'elastic'; // Default Wazuh indexer name export const WAZUH_INDEXER_NAME = 'Wazuh indexer'; +// Not timeFieldName on index pattern +export const NOT_TIME_FIELD_NAME_INDEX_PATTERN = + 'not_time_field_name_index_pattern'; + // Customization export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1048576; @@ -409,8 +416,10 @@ export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1 export enum SettingCategory { GENERAL, HEALTH_CHECK, + EXTENSIONS, MONITORING, STATISTICS, + VULNERABILITIES, SECURITY, CUSTOMIZATION, } @@ -546,6 +555,10 @@ export const PLUGIN_SETTINGS_CATEGORIES: { 'Basic app settings related to alerts index pattern, hide the manager alerts in the dashboards, logs level and more.', renderOrder: SettingCategory.GENERAL, }, + [SettingCategory.EXTENSIONS]: { + title: 'Initial display state of the modules of the new API host entries.', + description: 'Extensions.', + }, [SettingCategory.SECURITY]: { title: 'Security', description: 'Application security options such as unauthorized roles.', @@ -563,6 +576,12 @@ export const PLUGIN_SETTINGS_CATEGORIES: { 'Options related to the daemons manager monitoring job and their storage in indexes.', renderOrder: SettingCategory.STATISTICS, }, + [SettingCategory.VULNERABILITIES]: { + title: 'Vulnerabilities', + description: + 'Options related to the agent vulnerabilities monitoring job and its storage in indexes.', + renderOrder: SettingCategory.VULNERABILITIES, + }, [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: @@ -820,6 +839,33 @@ 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(); + }, + }, 'cron.prefix': { title: 'Cron prefix', description: 'Define the index prefix of predefined jobs.', @@ -1108,7 +1154,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { }, 'customization.logo.app': { title: 'App main logo', - description: `This logo is used as loading indicator while the user is logging into Wazuh API`, + description: `This logo is used as loading indicator while the user is logging into Wazuh API.`, category: SettingCategory.CUSTOMIZATION, type: EpluginSettingType.filepicker, defaultValue: '', @@ -1237,6 +1283,51 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { )(value); }, }, + 'customization.logo.sidebar': { + title: 'Navigation drawer logo', + description: `This is the logo for the app to display in the platform's navigation drawer, this is, the main sidebar collapsible menu.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.filepicker, + defaultValue: '', + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresReloadingBrowserTab: true, + options: { + file: { + type: 'image', + extensions: ['.jpeg', '.jpg', '.png', '.svg'], + size: { + maxBytes: + CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, + }, + recommended: { + dimensions: { + width: 80, + height: 80, + unit: 'px', + }, + }, + store: { + relativePathFileSystem: 'public/assets/custom/images', + filename: 'customization.logo.sidebar', + resolveStaticURL: (filename: string) => + `custom/images/${filename}?v=${Date.now()}`, + // ?v=${Date.now()} is used to force the browser to reload the image when a new file is uploaded + }, + }, + }, + validate: function (value) { + return SettingsValidator.compose( + SettingsValidator.filePickerFileSize({ + ...this.options.file.size, + meaningfulUnit: true, + }), + SettingsValidator.filePickerSupportedExtensions( + this.options.file.extensions, + ), + )(value); + }, + }, 'customization.reports.footer': { title: 'Reports footer', description: 'Set the footer of the reports.', @@ -1277,6 +1368,53 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate.bind(this) }); }, }, + disabled_roles: { + title: 'Disable roles', + description: 'Disabled the plugin visibility for users with the roles.', + category: SettingCategory.SECURITY, + type: EpluginSettingType.editor, + defaultValue: [], + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + editor: { + language: 'json', + }, + }, + uiFormTransformConfigurationValueToInputValue: function (value: any): any { + return JSON.stringify(value); + }, + uiFormTransformInputValueToConfigurationValue: function ( + value: string, + ): any { + try { + return JSON.parse(value); + } catch (error) { + return value; + } + }, + validate: SettingsValidator.json( + SettingsValidator.compose( + SettingsValidator.array( + SettingsValidator.compose( + SettingsValidator.isString, + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + ), + ), + ), + ), + validateBackend: function (schema) { + return schema.arrayOf( + schema.string({ + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + ), + }), + ); + }, + }, 'enrollment.dns': { title: 'Enrollment DNS', description: @@ -1305,6 +1443,398 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate }); }, }, + 'extensions.audit': { + title: 'System auditing', + description: 'Enable or disable the Audit tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.aws': { + title: 'Amazon AWS', + description: 'Enable or disable the Amazon (AWS) tab on Overview.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.ciscat': { + title: 'CIS-CAT', + description: 'Enable or disable the CIS-CAT tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.docker': { + title: 'Docker listener', + description: + 'Enable or disable the Docker listener tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.gcp': { + title: 'Google Cloud platform', + description: 'Enable or disable the Google Cloud Platform tab on Overview.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.gdpr': { + title: 'GDPR', + description: 'Enable or disable the GDPR tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.github': { + title: 'GitHub', + description: 'Enable or disable the GitHub tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.hipaa': { + title: 'HIPAA', + description: 'Enable or disable the HIPAA tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.nist': { + title: 'NIST', + description: + 'Enable or disable the NIST 800-53 tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.office': { + title: 'Office 365', + description: 'Enable or disable the Office 365 tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.oscap': { + title: 'OSCAP', + description: 'Enable or disable the Open SCAP tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.osquery': { + title: 'Osquery', + description: 'Enable or disable the Osquery tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.pci': { + title: 'PCI DSS', + description: 'Enable or disable the PCI DSS tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.tsc': { + title: 'TSC', + description: 'Enable or disable the TSC tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: true, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, + 'extensions.virustotal': { + title: 'Virustotal', + description: 'Enable or disable the VirusTotal tab on Overview and Agents.', + category: SettingCategory.EXTENSIONS, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: false, + 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(); + }, + }, hideManagerAlerts: { title: 'Hide manager alerts', description: 'Hide the alerts of the manager in every dashboard.', @@ -1720,6 +2250,36 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.number({ validate: this.validate.bind(this) }); }, }, + 'vulnerabilities.pattern': { + title: 'Index pattern', + description: 'Default index pattern to use for vulnerabilities.', + category: SettingCategory.VULNERABILITIES, + type: EpluginSettingType.text, + defaultValue: WAZUH_VULNERABILITIES_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: false, + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters( + '\\', + '/', + '?', + '"', + '<', + '>', + '|', + ',', + '#', + ), + ), + validateBackend: function (schema) { + return schema.string({ minLength: 1, validate: this.validate }); + }, + }, }; export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS; diff --git a/plugins/main/common/plugin-settings.test.ts b/plugins/main/common/plugin-settings.test.ts index 936650a89d..2ede6f322f 100644 --- a/plugins/main/common/plugin-settings.test.ts +++ b/plugins/main/common/plugin-settings.test.ts @@ -34,6 +34,8 @@ 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.'} @@ -208,6 +210,22 @@ describe('[settings] Input validation', () => { ${'wazuh.monitoring.shards'} | ${-1} | ${'Value should be greater or equal than 1.'} ${'wazuh.monitoring.shards'} | ${'1.2'} | ${'Number should be an integer.'} ${'wazuh.monitoring.shards'} | ${1.2} | ${'Number should be an integer.'} + ${'vulnerabilities.pattern'} | ${'test'} | ${undefined} + ${'vulnerabilities.pattern'} | ${'test*'} | ${undefined} + ${'vulnerabilities.pattern'} | ${''} | ${'Value can not be empty.'} + ${'vulnerabilities.pattern'} | ${'-test'} | ${"It can't start with: -, _, +, .."} + ${'vulnerabilities.pattern'} | ${'_test'} | ${"It can't start with: -, _, +, .."} + ${'vulnerabilities.pattern'} | ${'+test'} | ${"It can't start with: -, _, +, .."} + ${'vulnerabilities.pattern'} | ${'.test'} | ${"It can't start with: -, _, +, .."} + ${'vulnerabilities.pattern'} | ${'test\\'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test/'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test?'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test"'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test<'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test>'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test|'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test,'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} + ${'vulnerabilities.pattern'} | ${'test#'} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} `( '$setting | $value | $expectedValidation', ({ setting, value, expectedValidation }) => { diff --git a/plugins/main/public/components/health-check/components/check-result.tsx b/plugins/main/public/components/health-check/components/check-result.tsx index 814de4eb23..125a8badc1 100644 --- a/plugins/main/public/components/health-check/components/check-result.tsx +++ b/plugins/main/public/components/health-check/components/check-result.tsx @@ -11,7 +11,13 @@ * Find more information about this on the LICENSE file. * */ -import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react'; +import React, { + useEffect, + useState, + useCallback, + useMemo, + useRef, +} from 'react'; import { EuiDescriptionListDescription, EuiDescriptionListTitle, @@ -20,8 +26,14 @@ import { import InspectLogButton from './inspect-logs-button'; import ResultIcons from './result-icons'; - -type Result = 'loading' | 'ready' | 'error' | 'error_retry' | 'disabled' | 'waiting'; +type Result = + | 'loading' + | 'ready' + | 'warning' + | 'error' + | 'error_retry' + | 'disabled' + | 'waiting'; export function CheckResult(props) { const [result, setResult] = useState('waiting'); @@ -32,7 +44,7 @@ export function CheckResult(props) { const verboseInfoWithNotificationWasOpened = useRef(false); useEffect(() => { - if (props.shouldCheck && !props.isLoading && awaitForIsReady()){ + if (props.shouldCheck && !props.isLoading && awaitForIsReady()) { initCheck(); } else if (props.shouldCheck === false && !props.checksReady[props.name]) { setResult('disabled'); @@ -41,18 +53,30 @@ export function CheckResult(props) { }, [props.shouldCheck, props.isLoading, props.checksReady]); useEffect(() => { - if (isCheckFinished){ + if (isCheckFinished) { + const warnings = verboseInfo.filter(log => log.type === 'warning'); const errors = verboseInfo.filter(log => log.type === 'error'); - if(errors.length){ + if (errors.length > 0) { props.canRetry ? setResult('error_retry') : setResult('error'); - props.handleErrors(props.name, errors.map(({message}) => message)); - }else{ - setResult('ready'); - setAsReady(); + props.handleErrors( + props.name, + errors.map(({ message }) => message), + ); + } else { + if (warnings.length > 0) { + props.handleWarnings( + props.name, + warnings.map(({ message }) => message), + ); + setResult('warning'); + setAsReady(); + } else { + setResult('ready'); + setAsReady(); + } } } - }, [isCheckFinished]) - + }, [isCheckFinished]); const setAsReady = () => { props.handleCheckReady(props.name, true); @@ -62,24 +86,36 @@ export function CheckResult(props) { * validate if the current check is not started and if the depending checks are ready */ const awaitForIsReady = () => { - return !isCheckStarted && (props.awaitFor.length === 0 || props.awaitFor.every((check) => { - return props.checksReady[check]; - })) - } + return ( + !isCheckStarted && + (props.awaitFor.length === 0 || + props.awaitFor.every(check => { + return props.checksReady[check]; + })) + ); + }; - const checkLogger = useMemo(() => ({ - _log: (message: string, type: 'info' | 'error' | 'action' ) => { - setVerboseInfo(state => [...state, {message, type}]); - }, - info: (message: string) => checkLogger._log(message, 'info'), - error: (message: string) => checkLogger._log(message, 'error'), - action: (message: string) => checkLogger._log(message, 'action') - }), []); + const checkLogger = useMemo( + () => ({ + _log: ( + message: string, + type: 'info' | 'warning' | 'error' | 'action', + ) => { + setVerboseInfo(state => [...state, { message, type }]); + }, + info: (message: string) => checkLogger._log(message, 'info'), + warning: (message: string) => checkLogger._log(message, 'warning'), + error: (message: string) => checkLogger._log(message, 'error'), + action: (message: string) => checkLogger._log(message, 'action'), + }), + [], + ); const initCheck = async () => { setIsCheckStarted(true); setResult('loading'); setVerboseInfo([]); + props.cleanWarnings(props.name); props.cleanErrors(props.name); setIsCheckFinished(false); try { @@ -90,39 +126,53 @@ export function CheckResult(props) { setIsCheckFinished(true); }; - - const checkDidSomeAction = useMemo(() => verboseInfo.some(log => log.type === 'action'), [verboseInfo]); - const shouldShowNotification = checkDidSomeAction && !verboseInfoWithNotificationWasOpened.current; + const checkDidSomeAction = useMemo( + () => verboseInfo.some(log => log.type === 'action'), + [verboseInfo], + ); + const shouldShowNotification = + checkDidSomeAction && !verboseInfoWithNotificationWasOpened.current; const haveLogs = verboseInfo.length > 0; const switchVerboseDetails = useCallback(() => { - if(checkDidSomeAction && !verboseInfoWithNotificationWasOpened.current){ + if (checkDidSomeAction && !verboseInfoWithNotificationWasOpened.current) { verboseInfoWithNotificationWasOpened.current = true; - }; + } setVerboseIsOpen(state => !state); - },[checkDidSomeAction]); + }, [checkDidSomeAction]); - const showLogButton = (props.showLogButton && isCheckStarted && haveLogs); + const showLogButton = props.showLogButton && isCheckStarted && haveLogs; return ( <> - - {props.title} - + {props.title} -

- {showLogButton ? :<>} -

+

+ + {showLogButton ? ( + + ) : ( + <> + )} + +

{verboseInfo.length > 0 && ( - {verboseInfo.map(log => `${log.type.toUpperCase()}: ${log.message}`).join('\n')} + {verboseInfo + .map(log => `${log.type.toUpperCase()}: ${log.message}`) + .join('\n')} )} 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 0b2dfc191c..9b6a456c8f 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 @@ -22,8 +22,10 @@ exports[`Health Check container should render a Health check screen 1`] = ` canRetry={true} checksReady={Object {}} cleanErrors={[Function]} + cleanWarnings={[Function]} handleCheckReady={[Function]} handleErrors={[Function]} + handleWarnings={[Function]} isLoading={false} key="health_check_check_api" name="api" @@ -40,8 +42,10 @@ exports[`Health Check container should render a Health check screen 1`] = ` } checksReady={Object {}} cleanErrors={[Function]} + cleanWarnings={[Function]} handleCheckReady={[Function]} handleErrors={[Function]} + handleWarnings={[Function]} isLoading={false} key="health_check_check_setup" name="setup" @@ -55,8 +59,10 @@ exports[`Health Check container should render a Health check screen 1`] = ` canRetry={true} checksReady={Object {}} cleanErrors={[Function]} + cleanWarnings={[Function]} handleCheckReady={[Function]} handleErrors={[Function]} + handleWarnings={[Function]} isLoading={false} key="health_check_check_pattern" name="pattern" @@ -70,8 +76,10 @@ exports[`Health Check container should render a Health check screen 1`] = ` canRetry={true} checksReady={Object {}} cleanErrors={[Function]} + cleanWarnings={[Function]} handleCheckReady={[Function]} handleErrors={[Function]} + handleWarnings={[Function]} isLoading={false} key="health_check_check_patternMonitoring" name="patternMonitoring" @@ -85,8 +93,10 @@ exports[`Health Check container should render a Health check screen 1`] = ` canRetry={true} checksReady={Object {}} cleanErrors={[Function]} + cleanWarnings={[Function]} handleCheckReady={[Function]} handleErrors={[Function]} + handleWarnings={[Function]} isLoading={false} key="health_check_check_patternStatistics" name="patternStatistics" @@ -95,6 +105,23 @@ 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, }, }), useRootScope: () => ({}), @@ -108,4 +109,15 @@ describe('Health Check container', () => { const callOutError = component.find('EuiCallOut'); expect(callOutError.text()).toBe('[API version] Test error'); }); + + it('should render a Health check screen with warning', () => { + const component = mount(); + + component.find('CheckResult').at(1).invoke('handleWarnings')('setup', [ + 'Test warning', + ]); + + const callOutWarning = component.find('EuiCallOut'); + expect(callOutWarning.text()).toBe('[API version] Test warning'); + }); }); 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 885408f292..29c1252545 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,8 +33,10 @@ 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, } from '../../../../common/constants'; import { compose } from 'redux'; @@ -88,9 +90,23 @@ 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, + }, }; function HealthCheckComponent() { + const [checkWarnings, setCheckWarnings] = useState<{ [key: string]: [] }>({}); const [checkErrors, setCheckErrors] = useState<{ [key: string]: [] }>({}); const [checksReady, setChecksReady] = useState<{ [key: string]: boolean }>( {}, @@ -136,6 +152,18 @@ function HealthCheckComponent() { setIsDebugMode(window.location.href.includes('debug')); }, []); + const handleWarnings = (checkID, warnings, parsed) => { + const newWarnings = parsed + ? warnings.map(warning => + ErrorHandler.handle(warning, 'Health Check', { + warning: true, + silent: true, + }), + ) + : warnings; + setCheckWarnings(prev => ({ ...prev, [checkID]: newWarnings })); + }; + const handleErrors = (checkID, errors, parsed) => { const newErrors = parsed ? errors.map(error => @@ -148,6 +176,11 @@ function HealthCheckComponent() { setCheckErrors(prev => ({ ...prev, [checkID]: newErrors })); }; + const cleanWarnings = (checkID: string) => { + delete checkWarnings[checkID]; + setCheckWarnings({ ...checkWarnings }); + }; + const cleanErrors = (checkID: string) => { delete checkErrors[checkID]; setCheckErrors({ ...checkErrors }); @@ -164,9 +197,10 @@ function HealthCheckComponent() { : getThemeAssetURL('logo.svg'), ); const thereAreErrors = Object.keys(checkErrors).length > 0; + const thereAreWarnings = Object.keys(checkWarnings).length > 0; const renderChecks = () => { - const showLogButton = thereAreErrors || isDebugMode; + const showLogButton = thereAreErrors || thereAreWarnings || isDebugMode; return Object.keys(checks).map((check, index) => { return ( { + return Object.keys(checkWarnings).map(checkID => + checkWarnings[checkID].map((warning, index) => ( + + + {`[${checks[checkID].label}]`}{' '} + + + } + color='warning' + iconType='alert' + style={{ textAlign: 'left' }} + data-test-subj='callOutWarning' + > + + + )), + ); + }; + const renderErrors = () => { return Object.keys(checkErrors).map(checkID => checkErrors[checkID].map((error, index) => ( @@ -268,7 +328,13 @@ function HealthCheckComponent() { {renderErrors()} )} - {(thereAreErrors || isDebugMode) && ( + {thereAreWarnings && ( + <> + + {renderWarnings()} + + )} + {(thereAreErrors || thereAreWarnings || isDebugMode) && ( <> diff --git a/plugins/main/public/components/health-check/services/check-pattern-support.service.ts b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts index 747a811ad4..611f6fd1a2 100644 --- a/plugins/main/public/components/health-check/services/check-pattern-support.service.ts +++ b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts @@ -14,43 +14,67 @@ import { SavedObject } from '../../../react-services'; import { CheckLogger } from '../types/check_logger'; -export const checkPatternSupportService = (pattern: string, indexType : string) => async (checkLogger: CheckLogger) => { - checkLogger.info(`Checking index pattern id [${pattern}] exists...`); - const result = await SavedObject.existsIndexPattern(pattern); - checkLogger.info(`Exist index pattern id [${pattern}]: ${result.data ? 'yes' : 'no'}`); - - if (!result.data) { - checkLogger.info(`Getting indices fields for the index pattern id [${pattern}]...`); - const fields = await SavedObject.getIndicesFields(pattern, indexType); - checkLogger.info(`Fields for index pattern id [${pattern}] found: ${fields.length}`); - - try { - checkLogger.info(`Creating saved object for the index pattern with id [${pattern}]. +export const checkPatternSupportService = + (pattern: string, indexType: string, timeFieldName?: string) => + async (checkLogger: CheckLogger) => { + checkLogger.info(`Checking index pattern id [${pattern}] exists...`); + const result = await SavedObject.existsIndexPattern(pattern); + checkLogger.info( + `Exist index pattern id [${pattern}]: ${result.data ? 'yes' : 'no'}`, + ); + + if (!result.data) { + try { + checkLogger.info( + `Getting indices fields for the index pattern id [${pattern}]...`, + ); + const fields = await SavedObject.getIndicesFields(pattern, indexType); + if (fields) { + checkLogger.info( + `Fields for index pattern id [${pattern}] found: ${fields.length}`, + ); + checkLogger.info(`Creating saved object for the index pattern with id [${pattern}]. title: ${pattern} id: ${pattern} - timeFieldName: timestamp - ${fields ? `fields: ${fields.length}`: ''}`); - await SavedObject.createSavedObject( - 'index-pattern', - pattern, - { - attributes: { - title: pattern, - timeFieldName: 'timestamp' - } - }, - fields - ); - checkLogger.action(`Created the saved object for the index pattern id [${pattern}]`); - } catch (error) { - checkLogger.error(`Error creating index pattern id [${pattern}]: ${error.message || error}`); + timeFieldName: ${timeFieldName} + ${fields ? `fields: ${fields.length}` : ''}`); + await SavedObject.createSavedObject( + 'index-pattern', + pattern, + { + attributes: { + title: pattern, + timeFieldName, + }, + }, + fields, + ); + checkLogger.action( + `Created the saved object for the index pattern id [${pattern}]`, + ); + const indexPatternSavedObjectIDs = [pattern]; + // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint. + // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293 + checkLogger.info( + `Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join( + ',', + )} can be found...`, + ); + await SavedObject.validateIndexPatternSavedObjectCanBeFound( + indexPatternSavedObjectIDs, + ); + checkLogger.info('Integrity of saved objects: [ok]'); + } else { + checkLogger.warning( + `No indices fields found for index pattern id [${pattern}], it is necessary to check the index...`, + ); + } + } catch (error) { + checkLogger.error( + `Error creating index pattern id [${pattern}]: ${ + error.message || error + }`, + ); + } } }; - - const indexPatternSavedObjectIDs = [pattern]; - // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint. - // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293 - checkLogger.info(`Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join(',')} can be found...`); - await SavedObject.validateIndexPatternSavedObjectCanBeFound(indexPatternSavedObjectIDs); - checkLogger.info('Integrity of saved objects: [ok]'); -} diff --git a/plugins/main/public/components/health-check/types/check_logger.ts b/plugins/main/public/components/health-check/types/check_logger.ts index 75220289b4..e14c1893ae 100644 --- a/plugins/main/public/components/health-check/types/check_logger.ts +++ b/plugins/main/public/components/health-check/types/check_logger.ts @@ -1,7 +1,8 @@ export type CheckLog = (message: string) => void; export interface CheckLogger { - info: CheckLog - error: CheckLog - action: CheckLog -}; + info: CheckLog; + warning: CheckLog; + error: CheckLog; + action: CheckLog; +} diff --git a/plugins/main/public/components/health-check/types/result-icons-presets.ts b/plugins/main/public/components/health-check/types/result-icons-presets.ts index 09d457bd62..0151d88614 100644 --- a/plugins/main/public/components/health-check/types/result-icons-presets.ts +++ b/plugins/main/public/components/health-check/types/result-icons-presets.ts @@ -1,49 +1,55 @@ export type ResultIconProps = { - tooltipText?: string - iconColor?: string - iconType?: string - disabled?: boolean - spinner?: boolean - retry?: boolean + tooltipText?: string; + iconColor?: string; + iconType?: string; + disabled?: boolean; + spinner?: boolean; + retry?: boolean; }; export type ResultIconsPreset = { - disabled: ResultIconProps, - loading: ResultIconProps, - ready: ResultIconProps, - error: ResultIconProps, - error_retry: ResultIconProps, - waiting: ResultIconProps + disabled: ResultIconProps; + loading: ResultIconProps; + ready: ResultIconProps; + warning: ResultIconProps; + error: ResultIconProps; + error_retry: ResultIconProps; + waiting: ResultIconProps; }; export const resultsPreset: ResultIconsPreset = { - disabled: { - disabled: true - }, - loading: { - tooltipText: 'Checking...', - spinner: true, - iconType: '' - }, - ready: { - tooltipText: 'Ready', - iconColor: 'secondary', - iconType: 'check' - }, - error: { - tooltipText: 'Error', - iconColor: 'danger', - iconType: 'alert' - }, - error_retry: { - tooltipText: 'Error', - iconColor: 'danger', - iconType: 'alert', - retry: true - }, - waiting: { - tooltipText: 'On hold...', - iconColor: '#999999', - iconType: 'clock' - } -} \ No newline at end of file + disabled: { + disabled: true, + }, + loading: { + tooltipText: 'Checking...', + spinner: true, + iconType: '', + }, + ready: { + tooltipText: 'Ready', + iconColor: 'secondary', + iconType: 'check', + }, + warning: { + tooltipText: 'Warning', + iconColor: 'warning', + iconType: 'alert', + }, + error: { + tooltipText: 'Error', + iconColor: 'danger', + iconType: 'alert', + }, + error_retry: { + tooltipText: 'Error', + iconColor: 'danger', + iconType: 'alert', + retry: true, + }, + waiting: { + tooltipText: 'On hold...', + iconColor: '#999999', + iconType: 'clock', + }, +}; diff --git a/plugins/main/public/components/overview/vulnerabilities/common/constants.ts b/plugins/main/public/components/overview/vulnerabilities/common/constants.ts deleted file mode 100644 index 29536bb7f2..0000000000 --- a/plugins/main/public/components/overview/vulnerabilities/common/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const VULNERABILITIES_INDEX_PATTERN_ID = 'wazuh-states-vulnerabilities'; \ No newline at end of file diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index bdfb21ba0e..4f7cdc1518 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { getPlugins } from '../../../../../kibana-services'; -import useSearchBarConfiguration from '../../search_bar/use_search_bar_configuration' +import useSearchBarConfiguration from '../../search_bar/use_search_bar_configuration'; import { IntlProvider } from 'react-intl'; import { EuiDataGrid, @@ -16,7 +16,7 @@ import { EuiTitle, EuiButtonEmpty, EuiCallOut, - EuiSpacer + EuiSpacer, } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../../../src/core/server'; @@ -27,31 +27,45 @@ import { useDataGrid } from '../../data_grid/use_data_grid'; import { MAX_ENTRIES_PER_QUERY, inventoryTableDefaultColumns } from './config'; import { useDocViewer } from '../../doc_viewer/use_doc_viewer'; import './inventory.scss'; -import { VULNERABILITIES_INDEX_PATTERN_ID } from '../../common/constants'; import { search, exportSearchToCSV } from './inventory_service'; -import { ErrorHandler, ErrorFactory, HttpError } from '../../../../../react-services/error-management'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../../react-services/error-management'; import { withErrorBoundary } from '../../../../common/hocs'; import { HitsCounter } from '../../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; import { formatNumWithCommas } from '../../../../../kibana-integrations/discover/application/helpers'; +import { useAppConfig } from '../../../../common/hooks'; const InventoryVulsComponent = () => { + const appConfig = useAppConfig(); + const VULNERABILITIES_INDEX_PATTERN_ID = + appConfig.data['vulnerabilities.pattern']; const { searchBarProps } = useSearchBarConfiguration({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, - }) + }); const { isLoading, filters, query, indexPatterns } = searchBarProps; const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); - const [indexPattern, setIndexPattern] = useState(undefined); + const [indexPattern, setIndexPattern] = useState( + undefined, + ); const [isSearching, setIsSearching] = useState(false); const [isExporting, setIsExporting] = useState(false); - const onClickInspectDoc = useMemo(() => (index: number) => { - const rowClicked = results.hits.hits[index]; - setInspectedHit(rowClicked); - }, [results]); + const onClickInspectDoc = useMemo( + () => (index: number) => { + const rowClicked = results.hits.hits[index]; + setInspectedHit(rowClicked); + }, + [results], + ); - const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { const inspectHintMsg = 'Inspect document details'; return ( @@ -69,15 +83,15 @@ const InventoryVulsComponent = () => { defaultColumns: inventoryTableDefaultColumns, results, indexPattern: indexPattern as IndexPattern, - DocViewInspectButton - }) + DocViewInspectButton, + }); const { pagination, sorting, columnVisibility } = dataGridProps; const docViewerProps = useDocViewer({ doc: inspectedHit, indexPattern: indexPattern as IndexPattern, - }) + }); useEffect(() => { if (!isLoading) { @@ -87,19 +101,30 @@ const InventoryVulsComponent = () => { filters, query, pagination, - sorting - }).then((results) => { - setResults(results); - setIsSearching(false); - }).catch((error) => { - const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching vulnerabilities' }) - ErrorHandler.handleError(searchError); - setIsSearching(false); + sorting, }) + .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(pagination), JSON.stringify(sorting)]); + }, [ + JSON.stringify(searchBarProps), + JSON.stringify(pagination), + JSON.stringify(sorting), + ]); - const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; + const timeField = indexPattern?.timeFieldName + ? indexPattern.timeFieldName + : undefined; const onClickExportResults = async () => { const params = { @@ -109,43 +134,48 @@ const InventoryVulsComponent = () => { fields: columnVisibility.visibleColumns, pagination: { pageIndex: 0, - pageSize: results.hits.total + pageSize: results.hits.total, }, - sorting - } + sorting, + }; try { setIsExporting(true); await exportSearchToCSV(params); } catch (error) { - const searchError = ErrorFactory.create(HttpError, { error, message: 'Error downloading csv report' }) + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error downloading csv report', + }); ErrorHandler.handleError(searchError); - }finally{ + } finally { setIsExporting(false); } - } + }; return ( - + <> - {isLoading ? - : + {isLoading ? ( + + ) : ( } - {isSearching ? - : null} - {!isLoading && !isSearching && results?.hits?.total === 0 ? - : null} + /> + )} + {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} + onResetQuery={() => {}} + 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 + } /> + className='euiDataGrid__controlBtn' + onClick={onClickExportResults} + > Export Formated - ) + ), }} - />) : null} - {inspectedHit && ( - setInspectedHit(undefined)} size="m"> - - -

Document Details

-
-
- - - - - - - -
- )} - -
-
+ /> + ) : null} + {inspectedHit && ( + setInspectedHit(undefined)} size='m'> + + +

Document Details

+
+
+ + + + + + + +
+ )} + + +
); -} +}; -export const InventoryVuls = withErrorBoundary(InventoryVulsComponent); \ No newline at end of file +export const InventoryVuls = withErrorBoundary(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 e3810319c5..5328214a06 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx @@ -4,10 +4,10 @@ import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public' import { getDashboardPanels } from './dashboard_panels'; import { I18nProvider } from '@osd/i18n/react'; import useSearchBarConfiguration from '../../search_bar/use_search_bar_configuration'; -import { VULNERABILITIES_INDEX_PATTERN_ID } from '../../common/constants'; import { getDashboardFilters } from './dashboard_panels_filters'; import './vulnerability_detector_filters.scss'; import { getKPIsPanel } from './dashboard_panels_kpis'; +import { useAppConfig } from '../../../../common/hooks'; const plugins = getPlugins(); const SearchBar = getPlugins().data.ui.SearchBar; @@ -19,6 +19,10 @@ a wrapper for visual adjustments, while the Kpi, the Open vs Close visualization the rest of the visualizations have different configurations at the dashboard level. */ export const DashboardVuls: React.FC = () => { + const appConfig = useAppConfig(); + const VULNERABILITIES_INDEX_PATTERN_ID = + appConfig.data['vulnerabilities.pattern']; + const { searchBarProps } = useSearchBarConfiguration({ defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID, filters: [], @@ -28,18 +32,18 @@ export const DashboardVuls: React.FC = () => { <> -
+
{ { { return { @@ -296,7 +295,9 @@ const getVisStateTopVulnerabilitiesEndpoints = (indexPatternId: string) => { }; }; -const getVisStateAccumulationMostDetectedVulnerabilities = (indexPatternId: string) => { +const getVisStateAccumulationMostDetectedVulnerabilities = ( + indexPatternId: string, +) => { return { id: 'accumulation_most_vulnerable_vulnerabilities', title: 'Accumulation of the most detected vulnerabilities', @@ -615,8 +616,12 @@ const getVisStateInventoryTable = (indexPatternId: string) => { }; }; -export const getDashboardPanels = (): { - [panelId: string]: DashboardPanelState; +export const getDashboardPanels = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; } => { return { '6': { @@ -630,7 +635,7 @@ export const getDashboardPanels = (): { type: 'visualization', explicitInput: { id: '6', - savedVis: getVisStateTopVulnerabilities(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateTopVulnerabilities(indexPatternId), }, }, '7': { @@ -644,7 +649,7 @@ export const getDashboardPanels = (): { type: 'visualization', explicitInput: { id: '7', - savedVis: getVisStateTopVulnerabilitiesEndpoints(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateTopVulnerabilitiesEndpoints(indexPatternId), }, }, '8': { @@ -658,9 +663,8 @@ export const getDashboardPanels = (): { type: 'visualization', explicitInput: { id: '8', - savedVis: getVisStateAccumulationMostDetectedVulnerabilities( - VULNERABILITIES_INDEX_PATTERN_ID - ), + savedVis: + getVisStateAccumulationMostDetectedVulnerabilities(indexPatternId), }, }, '9': { @@ -674,7 +678,7 @@ export const getDashboardPanels = (): { type: 'visualization', explicitInput: { id: '9', - savedVis: getVisStateInventoryTable(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateInventoryTable(indexPatternId), }, }, }; 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 8e25e649f5..387d5d8f4f 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 @@ -1,6 +1,5 @@ import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; -import { VULNERABILITIES_INDEX_PATTERN_ID } from '../../common/constants'; const getVisStateFilter = ( id: string, @@ -69,7 +68,9 @@ const getVisStateFilter = ( }; }; -export const getDashboardFilters = (): { +export const getDashboardFilters = ( + indexPatternId: string, +): { [panelId: string]: DashboardPanelState< EmbeddableInput & { [k: string]: unknown } >; @@ -88,7 +89,7 @@ export const getDashboardFilters = (): { id: 'topPackageSelector', savedVis: getVisStateFilter( 'topPackageSelector', - VULNERABILITIES_INDEX_PATTERN_ID, + indexPatternId, 'Top Packages vulnerabilities', 'Package', 'package.name', @@ -108,7 +109,7 @@ export const getDashboardFilters = (): { id: 'topOSVulnerabilities', savedVis: getVisStateFilter( 'topOSVulnerabilities', - VULNERABILITIES_INDEX_PATTERN_ID, + indexPatternId, 'Top Operating system vulnerabilities', 'Operating system', 'host.os.name', @@ -128,7 +129,7 @@ export const getDashboardFilters = (): { id: 'topAgentVulnerabilities', savedVis: getVisStateFilter( 'topAgentVulnerabilities', - VULNERABILITIES_INDEX_PATTERN_ID, + indexPatternId, 'Agent filter', 'Agent', 'agent.id', @@ -148,7 +149,7 @@ export const getDashboardFilters = (): { id: 'topVulnerabilities', savedVis: getVisStateFilter( 'topVulnerabilities', - VULNERABILITIES_INDEX_PATTERN_ID, + indexPatternId, 'Top vulnerabilities', 'Vulnerability', 'vulnerability.id', diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts index d7950c46bd..e86749e9ec 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts @@ -1,6 +1,5 @@ import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; -import { VULNERABILITIES_INDEX_PATTERN_ID } from '../../common/constants'; const getVisStateSeverityCritical = (indexPatternId: string) => { return { @@ -349,7 +348,9 @@ const getVisStateSeverityLow = (indexPatternId: string) => { }; }; -export const getKPIsPanel = (): { +export const getKPIsPanel = ( + indexPatternId: string, +): { [panelId: string]: DashboardPanelState< EmbeddableInput & { [k: string]: unknown } >; @@ -366,7 +367,7 @@ export const getKPIsPanel = (): { type: 'visualization', explicitInput: { id: '1', - savedVis: getVisStateSeverityCritical(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateSeverityCritical(indexPatternId), }, }, '2': { @@ -380,7 +381,7 @@ export const getKPIsPanel = (): { type: 'visualization', explicitInput: { id: '2', - savedVis: getVisStateSeverityHigh(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateSeverityHigh(indexPatternId), }, }, '3': { @@ -394,7 +395,7 @@ export const getKPIsPanel = (): { type: 'visualization', explicitInput: { id: '3', - savedVis: getVisStateSeverityMedium(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateSeverityMedium(indexPatternId), }, }, '4': { @@ -408,7 +409,7 @@ export const getKPIsPanel = (): { type: 'visualization', explicitInput: { id: '4', - savedVis: getVisStateSeverityLow(VULNERABILITIES_INDEX_PATTERN_ID), + savedVis: getVisStateSeverityLow(indexPatternId), }, }, }; diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx index 335f17be53..eb3b64fee5 100644 --- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx @@ -12,15 +12,22 @@ import { } from '../../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../../kibana-services'; -import { useFilterManager, useQueryManager, useTimeFilter } from '../../../common/hooks'; +import { + useAppConfig, + useFilterManager, + useQueryManager, + useTimeFilter, +} from '../../../common/hooks'; import { AUTHORIZED_AGENTS } from '../../../../../common/constants'; -import { VULNERABILITIES_INDEX_PATTERN_ID } from '../common/constants'; // Input - types type tUseSearchBarCustomInputs = { defaultIndexPatternID?: IIndexPattern['id']; onFiltersUpdated?: (filters: Filter[]) => void; - onQuerySubmitted?: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + onQuerySubmitted?: ( + payload: { dateRange: TimeRange; query?: Query }, + isUpdate?: boolean, + ) => void; }; type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; @@ -34,24 +41,35 @@ type tUserSearchBarResponse = { * @param props * @returns */ -const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarResponse => { +const useSearchBarConfiguration = ( + props?: tUseSearchBarProps, +): tUserSearchBarResponse => { + const appConfig = useAppConfig(); + const VULNERABILITIES_INDEX_PATTERN_ID = + appConfig.data['vulnerabilities.pattern']; // dependencies const filterManager = useFilterManager().filterManager as FilterManager; const { filters } = useFilterManager(); - const [query, setQuery] = props?.query ? useState(props?.query) : useQueryManager(); + const [query, setQuery] = props?.query + ? useState(props?.query) + : useQueryManager(); const { timeFilter, timeHistory, setTimeFilter } = useTimeFilter(); // states const [isLoading, setIsLoading] = useState(false); - const [indexPatternSelected, setIndexPatternSelected] = useState(); + const [indexPatternSelected, setIndexPatternSelected] = + useState(); useEffect(() => { initSearchBar(); }, []); useEffect(() => { - const defaultIndex = props?.defaultIndexPatternID ?? VULNERABILITIES_INDEX_PATTERN_ID; + const defaultIndex = + props?.defaultIndexPatternID ?? VULNERABILITIES_INDEX_PATTERN_ID; /* Filters that do not belong to the default index are filtered */ - const cleanedFilters = filters.filter((filter) => filter.meta.index === defaultIndex); + const cleanedFilters = filters.filter( + filter => filter.meta.index === defaultIndex, + ); if (cleanedFilters.length !== filters.length) { filterManager.setFilters(cleanedFilters); } @@ -75,7 +93,8 @@ const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarRe * @returns */ const getIndexPattern = async (indexPatternID?: string) => { - const indexPatternService = getDataPlugin().indexPatterns as IndexPatternsContract; + const indexPatternService = getDataPlugin() + .indexPatterns as IndexPatternsContract; if (indexPatternID) { try { return await indexPatternService.get(indexPatternID); @@ -96,7 +115,8 @@ const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarRe * @returns */ const getInitialFilters = async (indexPattern: IIndexPattern) => { - const indexPatternService = getDataPlugin().indexPatterns as IndexPatternsContract; + const indexPatternService = getDataPlugin() + .indexPatterns as IndexPatternsContract; let initialFilters: Filter[] = []; if (props?.filters) { return props?.filters; @@ -105,8 +125,12 @@ const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarRe // get filtermanager and filters // if the index is the same, get filters stored // else clear filters - const defaultIndexPattern = (await indexPatternService.getDefault()) as IIndexPattern; - initialFilters = defaultIndexPattern.id === indexPattern.id ? filterManager.getFilters() : []; + const defaultIndexPattern = + (await indexPatternService.getDefault()) as IIndexPattern; + initialFilters = + defaultIndexPattern.id === indexPattern.id + ? filterManager.getFilters() + : []; } else { initialFilters = []; } @@ -120,7 +144,9 @@ const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarRe */ const getFilters = () => { const filters = filterManager ? filterManager.getFilters() : []; - return filters.filter((filter) => filter.meta.controlledBy !== AUTHORIZED_AGENTS); // remove auto loaded agent.id filters + return filters.filter( + filter => filter.meta.controlledBy !== AUTHORIZED_AGENTS, + ); // remove auto loaded agent.id filters }; /** @@ -141,7 +167,7 @@ const useSearchBarConfiguration = (props?: tUseSearchBarProps): tUserSearchBarRe }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, - _isUpdate?: boolean + _isUpdate?: boolean, ): void => { const { dateRange, query } = payload; // its necessary execute setter to apply query filters diff --git a/plugins/main/public/react-services/saved-objects.js b/plugins/main/public/react-services/saved-objects.js index 992fe96efa..f66fc25321 100644 --- a/plugins/main/public/react-services/saved-objects.js +++ b/plugins/main/public/react-services/saved-objects.js @@ -16,6 +16,7 @@ import { FieldsStatistics } from '../utils/statistics-fields'; import { FieldsMonitoring } from '../utils/monitoring-fields'; import { HEALTH_CHECK, + NOT_TIME_FIELD_NAME_INDEX_PATTERN, PLUGIN_PLATFORM_NAME, WAZUH_INDEX_TYPE_ALERTS, WAZUH_INDEX_TYPE_MONITORING, @@ -32,11 +33,16 @@ export class SavedObject { static async getListOfIndexPatterns() { const savedObjects = await GenericRequest.request( 'GET', - `/api/saved_objects/_find?type=index-pattern&fields=title&fields=fields&per_page=9999` - ); + `/api/saved_objects/_find?type=index-pattern&fields=title&fields=fields&per_page=9999`, + ); const indexPatterns = ((savedObjects || {}).data || {}).saved_objects || []; - return indexPatterns.map((indexPattern) => ({...indexPattern, _fields: indexPattern?.attributes?.fields ? JSON.parse(indexPattern.attributes.fields) : []})); + return indexPatterns.map(indexPattern => ({ + ...indexPattern, + _fields: indexPattern?.attributes?.fields + ? JSON.parse(indexPattern.attributes.fields) + : [], + })); } /** @@ -49,8 +55,8 @@ export class SavedObject { if (where === HEALTH_CHECK) { const list = await Promise.all( defaultIndexPatterns.map( - async (pattern) => await SavedObject.getExistingIndexPattern(pattern) - ) + async pattern => await SavedObject.getExistingIndexPattern(pattern), + ), ); result = this.validateIndexPatterns(list); } @@ -60,7 +66,7 @@ export class SavedObject { result = this.validateIndexPatterns(list); } - return result.map((item) => { + return result.map(item => { return { id: item.id, title: item.attributes.title }; }); } @@ -72,13 +78,23 @@ export class SavedObject { 'manager.name', 'agent.id', ]; - return list.filter(item => item && item._fields && requiredFields.every((reqField => item._fields.some(field => field.name === reqField)))); + return list.filter( + item => + item && + item._fields && + requiredFields.every(reqField => + item._fields.some(field => field.name === reqField), + ), + ); } static async existsOrCreateIndexPattern(patternID) { const result = await SavedObject.existsIndexPattern(patternID); if (!result.data) { - const fields = await SavedObject.getIndicesFields(patternID, WAZUH_INDEX_TYPE_ALERTS); + const fields = await SavedObject.getIndicesFields( + patternID, + WAZUH_INDEX_TYPE_ALERTS, + ); await this.createSavedObject( 'index-pattern', patternID, @@ -88,7 +104,7 @@ export class SavedObject { timeFieldName: 'timestamp', }, }, - fields + fields, ); } } @@ -101,10 +117,11 @@ export class SavedObject { try { const indexPatternData = await GenericRequest.request( 'GET', - `/api/saved_objects/index-pattern/${patternID}?fields=title&fields=fields` + `/api/saved_objects/index-pattern/${patternID}?fields=title&fields=fields`, ); - const title = (((indexPatternData || {}).data || {}).attributes || {}).title; + const title = (((indexPatternData || {}).data || {}).attributes || {}) + .title; const id = ((indexPatternData || {}).data || {}).id; if (title) { @@ -133,16 +150,20 @@ export class SavedObject { 'GET', `/api/saved_objects/index-pattern/${patternID}?fields=title&fields=fields`, null, - true + true, ); - const indexPatternFields = indexPatternData?.data?.attributes?.fields ? JSON.parse(indexPatternData.data.attributes.fields) : []; + const indexPatternFields = indexPatternData?.data?.attributes?.fields + ? JSON.parse(indexPatternData.data.attributes.fields) + : []; return { ...indexPatternData.data, ...{ _fields: indexPatternFields } }; } catch (error) { if (error && error.response && error.response.status == 404) return false; return Promise.reject( ((error || {}).data || {}).message || false ? new Error(error.data.message) - : new Error(error.message || `Error getting the '${patternID}' index pattern`) + : new Error( + error.message || `Error getting the '${patternID}' index pattern`, + ), ); } } @@ -152,20 +173,42 @@ export class SavedObject { const result = await GenericRequest.request( 'POST', `/api/saved_objects/${type}/${id}`, - params + { + ...params, + attributes: { + ...params?.attributes, + timeFieldName: + params?.attributes?.timeFieldName !== + NOT_TIME_FIELD_NAME_INDEX_PATTERN + ? params?.attributes?.timeFieldName + : undefined, + }, + }, ); if (type === 'index-pattern') { - await this.refreshFieldsOfIndexPattern(id, params.attributes.title, fields); + await this.refreshFieldsOfIndexPattern( + id, + params.attributes.title, + fields, + params?.attributes?.timeFieldName, + ); } return result; } catch (error) { - throw ((error || {}).data || {}).message || false ? new Error(error.data.message) : error; + throw ((error || {}).data || {}).message || false + ? new Error(error.data.message) + : error; } } - static async refreshFieldsOfIndexPattern(id, title, fields) { + static async refreshFieldsOfIndexPattern( + id, + title, + fields, + timeFieldName = 'timestamp', + ) { try { // same logic as plugin platform when a new index is created, you need to refresh it to see its fields // we force the refresh of the index by requesting its fields and the assign these fields @@ -175,13 +218,18 @@ export class SavedObject { { attributes: { fields: JSON.stringify(fields), - timeFieldName: 'timestamp', - title: title + timeFieldName: + timeFieldName !== NOT_TIME_FIELD_NAME_INDEX_PATTERN + ? timeFieldName + : undefined, + title: title, }, - } + }, ); } catch (error) { - throw ((error || {}).data || {}).message || false ? new Error(error.data.message) : error; + throw ((error || {}).data || {}).message || false + ? new Error(error.data.message) + : error; } } @@ -191,11 +239,15 @@ export class SavedObject { */ static async refreshIndexPattern(pattern, newFields = null) { try { - const fields = await SavedObject.getIndicesFields(pattern.title, WAZUH_INDEX_TYPE_ALERTS); + const fields = await SavedObject.getIndicesFields( + pattern.title, + WAZUH_INDEX_TYPE_ALERTS, + ); if (newFields && typeof newFields == 'object') - Object.keys(newFields).forEach((fieldName) => { - if (this.isValidField(newFields[fieldName])) fields.push(newFields[fieldName]); + Object.keys(newFields).forEach(fieldName => { + if (this.isValidField(newFields[fieldName])) + fields.push(newFields[fieldName]); }); await this.refreshFieldsOfIndexPattern(pattern.id, pattern.title, fields); @@ -231,7 +283,10 @@ export class SavedObject { */ static async createWazuhIndexPattern(pattern) { try { - const fields = await SavedObject.getIndicesFields(pattern, WAZUH_INDEX_TYPE_ALERTS); + const fields = await SavedObject.getIndicesFields( + pattern, + WAZUH_INDEX_TYPE_ALERTS, + ); await this.createSavedObject( 'index-pattern', pattern, @@ -248,7 +303,7 @@ export class SavedObject { sourceFilters: '[{"value":"@timestamp"}]', }, }, - fields + fields, ); return; } catch (error) { @@ -264,7 +319,7 @@ export class SavedObject { //we check if indices exist before creating the index pattern 'GET', `/api/index_patterns/_fields_for_wildcard?pattern=${pattern}&meta_fields=_source&meta_fields=_id&meta_fields=_type&meta_fields=_index&meta_fields=_score`, - {} + {}, ); return response.data.fields; } catch { @@ -284,18 +339,24 @@ export class SavedObject { * It is usefull to validate if the endpoint works as expected. Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293 * @param {string[]} indexPatternIDs */ - static async validateIndexPatternSavedObjectCanBeFound(indexPatternIDs){ + static async validateIndexPatternSavedObjectCanBeFound(indexPatternIDs) { const indexPatternsSavedObjects = await getSavedObjects().client.find({ type: 'index-pattern', fields: ['title'], - perPage: 10000 + perPage: 10000, }); - const indexPatternsSavedObjectsCanBeFound = indexPatternIDs - .every(indexPatternID => indexPatternsSavedObjects.savedObjects.some(savedObject => savedObject.id === indexPatternID)); + const indexPatternsSavedObjectsCanBeFound = indexPatternIDs.every( + indexPatternID => + indexPatternsSavedObjects.savedObjects.some( + savedObject => savedObject.id === indexPatternID, + ), + ); if (!indexPatternsSavedObjectsCanBeFound) { throw new Error(`Saved object for index pattern not found. -Restart the ${PLUGIN_PLATFORM_NAME} service to initialize the index. More information in troubleshooting.` -)}; +Restart the ${PLUGIN_PLATFORM_NAME} service to initialize the index. More information in troubleshooting guide: ${webDocumentationLink( + 'user-manual/wazuh-dashboard/troubleshooting.html#saved-object-for-index-pattern-not-found', + )}.`); + } } } 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 ff156065aa..5f23722d0f 100644 --- a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json +++ b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json @@ -94,6 +94,11 @@ "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" } ] }, @@ -130,6 +135,11 @@ "subTitle": "Default index pattern to use for Wazuh monitoring.", "label": "wazuh.monitoring.pattern" }, + { + "title": "Index pattern", + "subTitle": "Default index pattern to use for vulnerabilities.", + "label": "vulnerabilities.pattern" + }, { "title": "", "subTitle": "", @@ -177,6 +187,21 @@ } ] }, + { + "name": "Vulnerabilities", + "items": [ + { + "title": "Index pattern", + "subTitle": "Default index pattern to use for vulnerabilities.", + "label": "vulnerabilities.pattern" + }, + { + "title": "", + "subTitle": "", + "label": "" + } + ] + }, { "name": "Logo Customization", "items": [ diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index b4bbc5897e..5b690d1e06 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -48,6 +48,9 @@ export const WAZUH_STATISTICS_DEFAULT_STATUS = true; export const WAZUH_STATISTICS_DEFAULT_FREQUENCY = 900; export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; +// Wazuh vulnerabilities +export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; + // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; @@ -105,10 +108,7 @@ export const WAZUH_CONFIGURATION_CACHE_TIME = 10000; // time in ms; // Reserved ids for Users/Role mapping export const WAZUH_API_RESERVED_ID_LOWER_THAN = 100; -export const WAZUH_API_RESERVED_WUI_SECURITY_RULES = [ - 1, - 2 -]; +export const WAZUH_API_RESERVED_WUI_SECURITY_RULES = [1, 2]; // Wazuh data path const WAZUH_DATA_PLUGIN_PLATFORM_BASE_PATH = 'data'; @@ -321,7 +321,7 @@ export const PLUGIN_PLATFORM_WAZUH_DOCUMENTATION_URL_PATH_TROUBLESHOOTING = export const PLUGIN_PLATFORM_WAZUH_DOCUMENTATION_URL_PATH_APP_CONFIGURATION = 'user-manual/wazuh-dashboard/config-file.html'; export const PLUGIN_PLATFORM_URL_GUIDE = - 'https://opensearch.org/docs/1.2/opensearch/index/'; + 'https://opensearch.org/docs/2.10/about'; export const PLUGIN_PLATFORM_URL_GUIDE_TITLE = 'OpenSearch guide'; export const PLUGIN_PLATFORM_REQUEST_HEADERS = { @@ -405,6 +405,10 @@ export const ELASTIC_NAME = 'elastic'; // Default Wazuh indexer name export const WAZUH_INDEXER_NAME = 'Wazuh indexer'; +// Not timeFieldName on index pattern +export const NOT_TIME_FIELD_NAME_INDEX_PATTERN = + 'not_time_field_name_index_pattern'; + // Customization export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1048576; @@ -415,6 +419,7 @@ export enum SettingCategory { EXTENSIONS, MONITORING, STATISTICS, + VULNERABILITIES, SECURITY, CUSTOMIZATION, } @@ -510,12 +515,12 @@ export type TPluginSetting = { requiresRestartingPluginPlatform?: boolean; // Define options related to the `type`. options?: - | TPluginSettingOptionsEditor - | TPluginSettingOptionsFile - | TPluginSettingOptionsNumber - | TPluginSettingOptionsSelect - | TPluginSettingOptionsSwitch - | TPluginSettingOptionsTextArea; + | TPluginSettingOptionsEditor + | TPluginSettingOptionsFile + | TPluginSettingOptionsNumber + | TPluginSettingOptionsSelect + | TPluginSettingOptionsSwitch + | TPluginSettingOptionsTextArea; // Transform the input value. The result is saved in the form global state of Settings/Configuration uiFormTransformChangedInputValue?: (value: any) => any; // Transform the configuration value or default as initial value for the input in Settings/Configuration @@ -571,6 +576,12 @@ export const PLUGIN_SETTINGS_CATEGORIES: { 'Options related to the daemons manager monitoring job and their storage in indexes.', renderOrder: SettingCategory.STATISTICS, }, + [SettingCategory.VULNERABILITIES]: { + title: 'Vulnerabilities', + description: + 'Options related to the agent vulnerabilities monitoring job and its storage in indexes.', + renderOrder: SettingCategory.VULNERABILITIES, + }, [SettingCategory.CUSTOMIZATION]: { title: 'Custom branding', description: @@ -828,6 +839,33 @@ 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(); + }, + }, 'cron.prefix': { title: 'Cron prefix', description: 'Define the index prefix of predefined jobs.', @@ -1116,7 +1154,7 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { }, 'customization.logo.app': { title: 'App main logo', - description: `This logo is used in the app main menu, at the top left corner.`, + description: `This logo is used as loading indicator while the user is logging into Wazuh API.`, category: SettingCategory.CUSTOMIZATION, type: EpluginSettingType.filepicker, defaultValue: '', @@ -2212,6 +2250,36 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.number({ validate: this.validate.bind(this) }); }, }, + 'vulnerabilities.pattern': { + title: 'Index pattern', + description: 'Default index pattern to use for vulnerabilities.', + category: SettingCategory.VULNERABILITIES, + type: EpluginSettingType.text, + defaultValue: WAZUH_VULNERABILITIES_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: false, + validate: SettingsValidator.compose( + SettingsValidator.isNotEmptyString, + SettingsValidator.hasNoSpaces, + SettingsValidator.noLiteralString('.', '..'), + SettingsValidator.noStartsWithString('-', '_', '+', '.'), + SettingsValidator.hasNotInvalidCharacters( + '\\', + '/', + '?', + '"', + '<', + '>', + '|', + ',', + '#', + ), + ), + validateBackend: function (schema) { + return schema.string({ minLength: 1, validate: this.validate }); + }, + }, }; export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS; From 42ca072c4dd05aa0c6b3746f541cc4d773ac9482 Mon Sep 17 00:00:00 2001 From: jbiset Date: Thu, 23 Nov 2023 12:00:51 -0300 Subject: [PATCH 2/5] Update check-result test --- .../__snapshots__/check-result.test.tsx.snap | 2 ++ .../components/check-result.test.tsx | 20 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap b/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap index 1eb1fbc457..8b1d3e8143 100644 --- a/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap +++ b/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap @@ -7,8 +7,10 @@ exports[`Check result component should render a Check result screen 1`] = ` check={true} checksReady={Object {}} cleanErrors={[MockFunction]} + cleanWarnings={[MockFunction]} handleCheckReady={[MockFunction]} handleErrors={[MockFunction]} + handleWarnings={[MockFunction]} isLoading={false} name="test" title="Check Test" diff --git a/plugins/main/public/components/health-check/components/check-result.test.tsx b/plugins/main/public/components/health-check/components/check-result.test.tsx index 1ce0be74ea..b235810c4e 100644 --- a/plugins/main/public/components/health-check/components/check-result.test.tsx +++ b/plugins/main/public/components/health-check/components/check-result.test.tsx @@ -18,13 +18,15 @@ import { act } from 'react-dom/test-utils'; describe('Check result component', () => { const validationService = jest.fn(); + const handleWarnings = jest.fn(); const handleErrors = jest.fn(); const handleCheckReady = jest.fn(); + const cleanWarnings = jest.fn(); const cleanErrors = jest.fn(); const awaitForMyComponent = async (wrapper: any) => { await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 0)); + await new Promise(resolve => setTimeout(resolve, 0)); wrapper.update(); }); }; @@ -38,13 +40,15 @@ describe('Check result component', () => { awaitFor={[]} check={true} validationService={validationService} + handleWarnings={handleWarnings} handleErrors={handleErrors} isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanWarnings={cleanWarnings} cleanErrors={cleanErrors} canRetry={true} - /> + />, ); expect(component).toMatchSnapshot(); @@ -59,13 +63,15 @@ describe('Check result component', () => { awaitFor={[]} shouldCheck={true} validationService={validationService} + handleWarnings={handleWarnings} handleErrors={handleErrors} isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanWarnings={cleanWarnings} cleanErrors={cleanErrors} canRetry={true} - /> + />, ); await awaitForMyComponent(wrapper); @@ -85,13 +91,15 @@ describe('Check result component', () => { awaitFor={[]} shouldCheck={true} validationService={validationService} + handleWarnings={handleWarnings} handleErrors={handleErrors} isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanWarnings={cleanWarnings} cleanErrors={cleanErrors} canRetry={true} - /> + />, ); await awaitForMyComponent(wrapper); @@ -111,13 +119,15 @@ describe('Check result component', () => { awaitFor={[]} shouldCheck={true} validationService={validationService} + handleWarnings={handleWarnings} handleErrors={handleErrors} isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanWarnings={cleanWarnings} cleanErrors={cleanErrors} canRetry={false} - /> + />, ); expect(wrapper.find('ResultIcons').exists()).toBeTruthy(); From ce2915335d64634b4aecd26b47a3b0222850ad45 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Thu, 23 Nov 2023 17:00:38 +0100 Subject: [PATCH 3/5] Sync core and main plugin default configuration --- plugins/main/common/constants.ts | 489 ------------------------- plugins/wazuh-core/common/constants.ts | 489 ------------------------- 2 files changed, 978 deletions(-) diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 5b690d1e06..4949a30685 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -416,7 +416,6 @@ export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1 export enum SettingCategory { GENERAL, HEALTH_CHECK, - EXTENSIONS, MONITORING, STATISTICS, VULNERABILITIES, @@ -555,10 +554,6 @@ export const PLUGIN_SETTINGS_CATEGORIES: { 'Basic app settings related to alerts index pattern, hide the manager alerts in the dashboards, logs level and more.', renderOrder: SettingCategory.GENERAL, }, - [SettingCategory.EXTENSIONS]: { - title: 'Initial display state of the modules of the new API host entries.', - description: 'Extensions.', - }, [SettingCategory.SECURITY]: { title: 'Security', description: 'Application security options such as unauthorized roles.', @@ -1283,51 +1278,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { )(value); }, }, - 'customization.logo.sidebar': { - title: 'Navigation drawer logo', - description: `This is the logo for the app to display in the platform's navigation drawer, this is, the main sidebar collapsible menu.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.filepicker, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - options: { - file: { - type: 'image', - extensions: ['.jpeg', '.jpg', '.png', '.svg'], - size: { - maxBytes: - CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, - }, - recommended: { - dimensions: { - width: 80, - height: 80, - unit: 'px', - }, - }, - store: { - relativePathFileSystem: 'public/assets/custom/images', - filename: 'customization.logo.sidebar', - resolveStaticURL: (filename: string) => - `custom/images/${filename}?v=${Date.now()}`, - // ?v=${Date.now()} is used to force the browser to reload the image when a new file is uploaded - }, - }, - }, - validate: function (value) { - return SettingsValidator.compose( - SettingsValidator.filePickerFileSize({ - ...this.options.file.size, - meaningfulUnit: true, - }), - SettingsValidator.filePickerSupportedExtensions( - this.options.file.extensions, - ), - )(value); - }, - }, 'customization.reports.footer': { title: 'Reports footer', description: 'Set the footer of the reports.', @@ -1368,53 +1318,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate.bind(this) }); }, }, - disabled_roles: { - title: 'Disable roles', - description: 'Disabled the plugin visibility for users with the roles.', - category: SettingCategory.SECURITY, - type: EpluginSettingType.editor, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json', - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: any): any { - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): any { - try { - return JSON.parse(value); - } catch (error) { - return value; - } - }, - validate: SettingsValidator.json( - SettingsValidator.compose( - SettingsValidator.array( - SettingsValidator.compose( - SettingsValidator.isString, - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - ), - ), - ), - validateBackend: function (schema) { - return schema.arrayOf( - schema.string({ - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - }), - ); - }, - }, 'enrollment.dns': { title: 'Enrollment DNS', description: @@ -1443,398 +1346,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate }); }, }, - 'extensions.audit': { - title: 'System auditing', - description: 'Enable or disable the Audit tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.aws': { - title: 'Amazon AWS', - description: 'Enable or disable the Amazon (AWS) tab on Overview.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.ciscat': { - title: 'CIS-CAT', - description: 'Enable or disable the CIS-CAT tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.docker': { - title: 'Docker listener', - description: - 'Enable or disable the Docker listener tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.gcp': { - title: 'Google Cloud platform', - description: 'Enable or disable the Google Cloud Platform tab on Overview.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.gdpr': { - title: 'GDPR', - description: 'Enable or disable the GDPR tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.github': { - title: 'GitHub', - description: 'Enable or disable the GitHub tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.hipaa': { - title: 'HIPAA', - description: 'Enable or disable the HIPAA tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.nist': { - title: 'NIST', - description: - 'Enable or disable the NIST 800-53 tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.office': { - title: 'Office 365', - description: 'Enable or disable the Office 365 tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.oscap': { - title: 'OSCAP', - description: 'Enable or disable the Open SCAP tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.osquery': { - title: 'Osquery', - description: 'Enable or disable the Osquery tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.pci': { - title: 'PCI DSS', - description: 'Enable or disable the PCI DSS tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.tsc': { - title: 'TSC', - description: 'Enable or disable the TSC tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.virustotal': { - title: 'Virustotal', - description: 'Enable or disable the VirusTotal tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, hideManagerAlerts: { title: 'Hide manager alerts', description: 'Hide the alerts of the manager in every dashboard.', diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index 5b690d1e06..4949a30685 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -416,7 +416,6 @@ export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1 export enum SettingCategory { GENERAL, HEALTH_CHECK, - EXTENSIONS, MONITORING, STATISTICS, VULNERABILITIES, @@ -555,10 +554,6 @@ export const PLUGIN_SETTINGS_CATEGORIES: { 'Basic app settings related to alerts index pattern, hide the manager alerts in the dashboards, logs level and more.', renderOrder: SettingCategory.GENERAL, }, - [SettingCategory.EXTENSIONS]: { - title: 'Initial display state of the modules of the new API host entries.', - description: 'Extensions.', - }, [SettingCategory.SECURITY]: { title: 'Security', description: 'Application security options such as unauthorized roles.', @@ -1283,51 +1278,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { )(value); }, }, - 'customization.logo.sidebar': { - title: 'Navigation drawer logo', - description: `This is the logo for the app to display in the platform's navigation drawer, this is, the main sidebar collapsible menu.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.filepicker, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - options: { - file: { - type: 'image', - extensions: ['.jpeg', '.jpg', '.png', '.svg'], - size: { - maxBytes: - CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, - }, - recommended: { - dimensions: { - width: 80, - height: 80, - unit: 'px', - }, - }, - store: { - relativePathFileSystem: 'public/assets/custom/images', - filename: 'customization.logo.sidebar', - resolveStaticURL: (filename: string) => - `custom/images/${filename}?v=${Date.now()}`, - // ?v=${Date.now()} is used to force the browser to reload the image when a new file is uploaded - }, - }, - }, - validate: function (value) { - return SettingsValidator.compose( - SettingsValidator.filePickerFileSize({ - ...this.options.file.size, - meaningfulUnit: true, - }), - SettingsValidator.filePickerSupportedExtensions( - this.options.file.extensions, - ), - )(value); - }, - }, 'customization.reports.footer': { title: 'Reports footer', description: 'Set the footer of the reports.', @@ -1368,53 +1318,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate.bind(this) }); }, }, - disabled_roles: { - title: 'Disable roles', - description: 'Disabled the plugin visibility for users with the roles.', - category: SettingCategory.SECURITY, - type: EpluginSettingType.editor, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json', - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: any): any { - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): any { - try { - return JSON.parse(value); - } catch (error) { - return value; - } - }, - validate: SettingsValidator.json( - SettingsValidator.compose( - SettingsValidator.array( - SettingsValidator.compose( - SettingsValidator.isString, - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - ), - ), - ), - validateBackend: function (schema) { - return schema.arrayOf( - schema.string({ - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - }), - ); - }, - }, 'enrollment.dns': { title: 'Enrollment DNS', description: @@ -1443,398 +1346,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.string({ validate: this.validate }); }, }, - 'extensions.audit': { - title: 'System auditing', - description: 'Enable or disable the Audit tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.aws': { - title: 'Amazon AWS', - description: 'Enable or disable the Amazon (AWS) tab on Overview.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.ciscat': { - title: 'CIS-CAT', - description: 'Enable or disable the CIS-CAT tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.docker': { - title: 'Docker listener', - description: - 'Enable or disable the Docker listener tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.gcp': { - title: 'Google Cloud platform', - description: 'Enable or disable the Google Cloud Platform tab on Overview.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.gdpr': { - title: 'GDPR', - description: 'Enable or disable the GDPR tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.github': { - title: 'GitHub', - description: 'Enable or disable the GitHub tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.hipaa': { - title: 'HIPAA', - description: 'Enable or disable the HIPAA tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.nist': { - title: 'NIST', - description: - 'Enable or disable the NIST 800-53 tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.office': { - title: 'Office 365', - description: 'Enable or disable the Office 365 tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.oscap': { - title: 'OSCAP', - description: 'Enable or disable the Open SCAP tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.osquery': { - title: 'Osquery', - description: 'Enable or disable the Osquery tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.pci': { - title: 'PCI DSS', - description: 'Enable or disable the PCI DSS tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.tsc': { - title: 'TSC', - description: 'Enable or disable the TSC tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, - 'extensions.virustotal': { - title: 'Virustotal', - description: 'Enable or disable the VirusTotal tab on Overview and Agents.', - category: SettingCategory.EXTENSIONS, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - 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(); - }, - }, hideManagerAlerts: { title: 'Hide manager alerts', description: 'Hide the alerts of the manager in every dashboard.', From 530b85b2540656fdab7aa2648e4bf51761d33a90 Mon Sep 17 00:00:00 2001 From: jbiset Date: Thu, 23 Nov 2023 13:55:05 -0300 Subject: [PATCH 4/5] Fix useSearchBarConfiguration for a test --- .../search_bar/use_search_bar_configuration.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx index eb3b64fee5..0ecee9b3a3 100644 --- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx @@ -44,9 +44,6 @@ type tUserSearchBarResponse = { const useSearchBarConfiguration = ( props?: tUseSearchBarProps, ): tUserSearchBarResponse => { - const appConfig = useAppConfig(); - const VULNERABILITIES_INDEX_PATTERN_ID = - appConfig.data['vulnerabilities.pattern']; // dependencies const filterManager = useFilterManager().filterManager as FilterManager; const { filters } = useFilterManager(); @@ -64,8 +61,7 @@ const useSearchBarConfiguration = ( }, []); useEffect(() => { - const defaultIndex = - props?.defaultIndexPatternID ?? VULNERABILITIES_INDEX_PATTERN_ID; + const defaultIndex = props?.defaultIndexPatternID; /* Filters that do not belong to the default index are filtered */ const cleanedFilters = filters.filter( filter => filter.meta.index === defaultIndex, From 6f8275d156d70fcba101d09baf10905957c74501 Mon Sep 17 00:00:00 2001 From: jbiset Date: Thu, 23 Nov 2023 14:46:09 -0300 Subject: [PATCH 5/5] Fix useSearchBarConfiguration --- .../search_bar/use_search_bar_configuration.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx index 0ecee9b3a3..377f24930c 100644 --- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx @@ -13,7 +13,6 @@ import { import { getDataPlugin } from '../../../../kibana-services'; import { - useAppConfig, useFilterManager, useQueryManager, useTimeFilter, @@ -22,7 +21,7 @@ import { AUTHORIZED_AGENTS } from '../../../../../common/constants'; // Input - types type tUseSearchBarCustomInputs = { - defaultIndexPatternID?: IIndexPattern['id']; + defaultIndexPatternID: IIndexPattern['id']; onFiltersUpdated?: (filters: Filter[]) => void; onQuerySubmitted?: ( payload: { dateRange: TimeRange; query?: Query },