From 665ecc49d301c83941ec5fd57655ec326a3f6b57 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Tue, 25 Oct 2022 12:37:33 +0200 Subject: [PATCH] [Backport 4.4-2.3-wzd] Centralize the plugin settings (#4501) (#4752) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Centralize the plugin settings (#4501) * feat(settings): centralize the plugin settings Create the plugin setting schema Define the current plugin settings Remove refactored code * feat(settings): add setting services and replaced the references to constants * feat(settings): refactor the content of the default configuration file Use dynamically the definition of the plugin settings * feat(inputs): create new inputs components Add new hooks to manage when a input value or form has changed Add new inputs components * feat(configuration): refactor the form of Settings/Configuration Refactor Header, BottomBar, Configuration components Remove deprecated files * feat(settings): support updating multiple setting at the same time Changed the endpoint that updating the plugin setting to support multiple settings at the same time Refactor the getConfiguration service. Split the logic to: - Read the file and transform to JSON - Obfuscate the password key of the host configuration * clean: remove not used code * fix: fixed category name in `Settings/Configuration` * fix(settings): fixed error due to missing service * fix(settings): refactor the form and inputs of `Settings/Configuration` to control the global state of the form * fix: add value transformation for the form inputs and output of fields changed * fix(settings): renamed properties related to transform the value of the input * feat(settings): add description to the plugin setting definition properties * fix(settings): fix getConfiguration service when the configuration file has no `hosts` entry * fix(settings): Fixed error when do changes of the `useForm` hook an rename methods of this * tests(settings): add test related to the plugin settings and its configuration from the UI * feat(settings): rename plugin setting options of type select to match its type * feat(settings): add plugin settings services and enhance the description of the plugin settings in default configuration file and UI * tests(input-form): update tests of InputForm component * test(configuration-file): add tests of the default configuration file * feat(settings): remove `extensions.mitre` plugin setting * feat(settings): add documentation to some setting services and test some of them * fix: fixed documentation of setting service * doc(settings): move the documentation of the plugin setting properties * fix(settings): rename some plugin setting properties because of request changes - Rename plugin setting properties: - `default` to `defaultValue` - `defaultHidden` to `defaultValueIfNotSet` - `configurableFile` to `isConfigurableFromFile` - configurableUI` to `isConfigurableFromUI` - `requireHealthCheck` to `requiresRunningHealthCheck` - `requireReload` to `requiresReloadingBrowserTab` - `requireRestart` to `requiresRestartingPluginPlatform` - Fix tests * tests: fix tests of InputForm component * fix: response properties when saving the configuration * fix(settings): fix displaying toast to run the healthcheck when saving the configuration * Added category sorting + description + docs link * Added settings sorting within their category * Fixed constant types definition * Checks if localCompare exists validation * fix(settings): fixed plugin setting description doesn't display the minimum number value when it is falsy (0) * fix(settings): fix setting type of `wazuh.monitoring.replicas` and limit the valid number for the number input * feat(settins): add plugin settings category description * fix(settings): fix a problem comparing the initial and current value for the plugin settings of the `number` type * fix(settings): fix typo in setting description * fix(tests): format tables of the tests * Fix small typo * fix(settings): fix a typo in a toast related to modify the plugin settings from UI * Changed Custom Branding documentation link * Delete unused imports * Update constants.ts Co-authored-by: Federico Rodriguez Co-authored-by: Álex (cherry picked from commit 430fe8299b755b53b29535f0cce98563e94ae5cc) * fix: fix some problems related to the conflict resolution and remove unused security factories * fix(test): fix tests related to the conflict resolution * fix(test): fix a failed test due to missing dependency Co-authored-by: Antonio <34042064+Desvelao@users.noreply.github.com> Co-authored-by: Antonio David Gutiérrez --- common/constants.ts | 1105 +++++++++++++++-- common/services/settings.test.ts | 34 + common/services/settings.ts | 124 ++ .../form/__snapshots__/index.test.tsx.snap | 253 ++++ public/components/common/form/hooks.test.tsx | 137 ++ public/components/common/form/hooks.tsx | 86 ++ public/components/common/form/index.test.tsx | 32 + public/components/common/form/index.tsx | 47 + .../components/common/form/input_editor.tsx | 15 + .../components/common/form/input_number.tsx | 14 + .../components/common/form/input_select.tsx | 13 + .../components/common/form/input_switch.tsx | 16 + public/components/common/form/input_text.tsx | 14 + public/components/common/form/types.ts | 18 + .../subrequirements/subrequirements.tsx | 6 +- .../components/techniques/techniques.tsx | 6 +- .../configuration/components/bottom-bar.tsx | 150 +-- .../components/categories/categories.tsx | 46 - .../components/category/category.tsx | 118 +- .../__snapshots__/field-form.test.tsx.snap | 67 - .../category/components/field-form.test.tsx | 39 - .../category/components/field-form.tsx | 175 --- .../components/category/components/index.ts | 14 - .../components/categories/index.ts | 13 - .../configuration/components/header.tsx | 42 +- .../configuration/components/index.ts | 1 - .../settings/configuration/configuration.tsx | 249 +++- .../utils/configuration-handler.js | 34 - .../wz-agent-selector/wz-agent-selector.js | 4 +- public/controllers/agent/agents.js | 5 +- .../overview/components/alerts-stats.js | 4 +- .../overview-actions/overview-actions.js | 4 +- public/controllers/overview/overview.js | 5 +- public/kibana-integrations/kibana-discover.js | 29 +- public/redux/reducers/appConfigReducers.ts | 4 +- public/services/resolves/get-config.js | 4 +- server/controllers/wazuh-api.ts | 2 +- server/controllers/wazuh-elastic.ts | 7 +- server/controllers/wazuh-utils/wazuh-utils.ts | 87 +- server/lib/get-configuration.ts | 59 +- server/lib/initial-wazuh-config.test.ts | 104 ++ server/lib/initial-wazuh-config.ts | 330 ++--- server/lib/reporting/audit-request.ts | 8 +- server/lib/reporting/extended-information.ts | 4 +- server/lib/reporting/gdpr-request.ts | 10 +- server/lib/reporting/overview-request.ts | 4 +- server/lib/reporting/pci-request.ts | 6 +- server/lib/reporting/rootcheck-request.ts | 8 +- server/lib/reporting/summary-table.ts | 4 +- server/lib/reporting/syscheck-request.ts | 10 +- server/lib/reporting/tsc-request.ts | 6 +- server/lib/reporting/vulnerability-request.ts | 14 +- .../factories/opendistro-factory.ts | 30 - .../factories/xpack-factory.ts | 33 - server/lib/update-configuration.ts | 37 +- server/routes/wazuh-api-http-status.test.ts | 2 +- server/routes/wazuh-utils/wazuh-utils.test.ts | 234 +++- server/routes/wazuh-utils/wazuh-utils.ts | 5 +- .../start/cron-scheduler/scheduler-handler.ts | 7 +- server/start/initialize/index.ts | 31 +- server/start/monitoring/index.ts | 161 ++- .../apps/overview/_integrity_monitoring.ts | 4 +- .../apps/overview/_security_events.ts | 4 +- test/server/wazuh-elastic.js | 16 +- 64 files changed, 2840 insertions(+), 1314 deletions(-) create mode 100644 common/services/settings.test.ts create mode 100644 common/services/settings.ts create mode 100644 public/components/common/form/__snapshots__/index.test.tsx.snap create mode 100644 public/components/common/form/hooks.test.tsx create mode 100644 public/components/common/form/hooks.tsx create mode 100644 public/components/common/form/index.test.tsx create mode 100644 public/components/common/form/index.tsx create mode 100644 public/components/common/form/input_editor.tsx create mode 100644 public/components/common/form/input_number.tsx create mode 100644 public/components/common/form/input_select.tsx create mode 100644 public/components/common/form/input_switch.tsx create mode 100644 public/components/common/form/input_text.tsx create mode 100644 public/components/common/form/types.ts delete mode 100644 public/components/settings/configuration/components/categories/categories.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/field-form.tsx delete mode 100644 public/components/settings/configuration/components/categories/components/category/components/index.ts delete mode 100644 public/components/settings/configuration/components/categories/index.ts delete mode 100644 public/components/settings/configuration/utils/configuration-handler.js create mode 100644 server/lib/initial-wazuh-config.test.ts delete mode 100644 server/lib/security-factory/factories/opendistro-factory.ts delete mode 100644 server/lib/security-factory/factories/xpack-factory.ts diff --git a/common/constants.ts b/common/constants.ts index d9b1e194c6..898287b81f 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -14,7 +14,7 @@ import { version } from '../package.json'; // Plugin export const PLUGIN_VERSION = version; -export const PLUGIN_VERSION_SHORT = version.split('.').splice(0,2).join('.'); +export const PLUGIN_VERSION_SHORT = version.split('.').splice(0, 2).join('.'); // Index patterns - Wazuh alerts export const WAZUH_INDEX_TYPE_ALERTS = 'alerts'; @@ -98,28 +98,6 @@ export const WAZUH_SECURITY_PLUGINS = [ // App configuration export const WAZUH_CONFIGURATION_CACHE_TIME = 10000; // time in ms; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART = [ - 'wazuh.monitoring.enabled', - 'wazuh.monitoring.frequency', - 'cron.statistics.interval', - 'logs.level', -]; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK = [ - 'pattern', - 'wazuh.monitoring.replicas', - 'wazuh.monitoring.creation', - 'wazuh.monitoring.pattern', - 'alerts.sample.prefix', - 'cron.statistics.index.name', - 'cron.statistics.index.creation', - 'cron.statistics.index.shards', - 'cron.statistics.index.replicas', - 'wazuh.monitoring.shards', -]; -export const WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD = [ - 'hideManagerAlerts', - 'customization.logo.sidebar' -]; // Reserved ids for Users/Role mapping export const WAZUH_API_RESERVED_ID_LOWER_THAN = 100; @@ -174,60 +152,6 @@ export const WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH = path.join( // Queue export const WAZUH_QUEUE_CRON_FREQ = '*/15 * * * * *'; // Every 15 seconds -// Default App Config -export const WAZUH_DEFAULT_APP_CONFIG = { - pattern: WAZUH_ALERTS_PATTERN, - 'checks.pattern': true, - 'checks.template': true, - 'checks.api': true, - 'checks.setup': true, - 'checks.fields': true, - 'checks.metaFields': true, - 'checks.maxBuckets': true, - 'checks.timeFilter': true, - 'extensions.pci': true, - 'extensions.gdpr': true, - 'extensions.hipaa': true, - 'extensions.nist': true, - 'extensions.tsc': true, - 'extensions.audit': true, - 'extensions.oscap': false, - 'extensions.ciscat': false, - 'extensions.aws': false, - 'extensions.office': false, - 'extensions.github': false, - 'extensions.gcp': false, - 'extensions.virustotal': false, - 'extensions.osquery': false, - 'extensions.docker': false, - timeout: 20000, - 'ip.selector': true, - 'ip.ignore': [], - 'wazuh.monitoring.enabled': WAZUH_MONITORING_DEFAULT_ENABLED, - 'wazuh.monitoring.frequency': WAZUH_MONITORING_DEFAULT_FREQUENCY, - 'wazuh.monitoring.shards': WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - 'wazuh.monitoring.replicas': WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, - 'wazuh.monitoring.creation': WAZUH_MONITORING_DEFAULT_CREATION, - 'wazuh.monitoring.pattern': WAZUH_MONITORING_PATTERN, - 'cron.prefix': WAZUH_STATISTICS_DEFAULT_PREFIX, - 'cron.statistics.status': WAZUH_STATISTICS_DEFAULT_STATUS, - 'cron.statistics.apis': [], - 'cron.statistics.interval': WAZUH_STATISTICS_DEFAULT_CRON_FREQ, - 'cron.statistics.index.name': WAZUH_STATISTICS_DEFAULT_NAME, - 'cron.statistics.index.creation': WAZUH_STATISTICS_DEFAULT_CREATION, - 'cron.statistics.index.shards': WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - 'cron.statistics.index.replicas': WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, - 'alerts.sample.prefix': WAZUH_SAMPLE_ALERT_PREFIX, - hideManagerAlerts: false, - 'logs.level': 'info', - 'enrollment.dns': '', - 'enrollment.password': '', - 'customization.logo.app': '', - 'customization.logo.sidebar': '', - 'customization.logo.healthcheck':'', - 'customization.logo.reports': '' -}; - // Wazuh errors export const WAZUH_ERROR_DAEMONS_NOT_READY = 'ERROR3099'; @@ -415,6 +339,1033 @@ export const DOCUMENTATION_WEB_BASE_URL = "https://documentation.wazuh.com"; // Default Elasticsearch user name context export const ELASTIC_NAME = 'elastic'; +// Plugin settings +export enum SettingCategory { + GENERAL, + HEALTH_CHECK, + EXTENSIONS, + MONITORING, + STATISTICS, + SECURITY, + CUSTOMIZATION, +}; + +type TPluginSettingOptionsSelect = { + select: { text: string, value: any }[] +}; + +type TPluginSettingOptionsNumber = { + number: { + min?: number + max?: number + } +}; + +type TPluginSettingOptionsEditor = { + editor: { + language: string + } +}; + +type TPluginSettingOptionsSwitch = { + switch: { + values: { + disabled: { label?: string, value: any }, + enabled: { label?: string, value: any }, + } + } +}; + +export enum EpluginSettingType { + text = 'text', + textarea = 'textarea', + switch = 'switch', + number = 'number', + editor = 'editor', + select = 'select', +}; + +export type TPluginSetting = { + // Define the text displayed in the UI. + title: string + // Description. + description: string + // Category. + category: SettingCategory + // Type. + type: EpluginSettingType + // Default value. + defaultValue: any + // Default value if it is not set. It has preference over `default`. + defaultValueIfNotSet?: any + // Configurable from the configuration file. + isConfigurableFromFile: boolean + // Configurable from the UI (Settings/Configuration). + isConfigurableFromUI: boolean + // Modify the setting requires running the plugin health check (frontend). + requiresRunningHealthCheck?: boolean + // Modify the setting requires reloading the browser tab (frontend). + requiresReloadingBrowserTab?: boolean + // Modify the setting requires restarting the plugin platform to take effect. + requiresRestartingPluginPlatform?: boolean + // Define options related to the `type`. + options?: TPluginSettingOptionsNumber | TPluginSettingOptionsEditor | TPluginSettingOptionsSelect | TPluginSettingOptionsSwitch + // 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 + uiFormTransformConfigurationValueToInputValue?: (value: any) => any + // Transform the input value changed in the form of Settings/Configuration and returned in the `changed` property of the hook useForm + uiFormTransformInputValueToConfigurationValue?: (value: any) => any +}; + +export type TPluginSettingWithKey = TPluginSetting & { key: TPluginSettingKey }; +export type TPluginSettingCategory = { + title: string + description?: string + documentationLink?: string + renderOrder?: number +}; + + +export const PLUGIN_SETTINGS_CATEGORIES: { [category: number]: TPluginSettingCategory } = { + [SettingCategory.HEALTH_CHECK]: { + title: 'Health check', + description: "Checks will be executed by the app's Healthcheck.", + renderOrder: SettingCategory.HEALTH_CHECK, + }, + [SettingCategory.GENERAL]: { + title: 'General', + description: "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.", + renderOrder: SettingCategory.SECURITY, + }, + [SettingCategory.MONITORING]: { + title: 'Task:Monitoring', + description: "Options related to the agent status monitoring job and its storage in indexes.", + renderOrder: SettingCategory.MONITORING, + }, + [SettingCategory.STATISTICS]: { + title: 'Task:Statistics', + description: "Options related to the daemons manager monitoring job and their storage in indexes..", + renderOrder: SettingCategory.STATISTICS, + }, + [SettingCategory.CUSTOMIZATION]: { + title: 'Custom branding', + description: "If you want to use custom branding elements such as logos, you can do so by editing the settings below.", + documentationLink: 'user-manual/wazuh-dashboard/white-labeling.html', + renderOrder: SettingCategory.CUSTOMIZATION, + } +}; + +export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { + "alerts.sample.prefix": { + title: "Sample alerts prefix", + description: "Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: WAZUH_SAMPLE_ALERT_PREFIX, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "checks.api": { + title: "API connection", + description: "Enable or disable the API 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); + }, + }, + "checks.fields": { + title: "Known fields", + description: "Enable or disable the known fields 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); + }, + }, + "checks.maxBuckets": { + title: "Set max buckets to 200000", + description: "Change the default value of the plugin platform max buckets configuration.", + 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); + }, + }, + "checks.metaFields": { + title: "Remove meta fields", + description: "Change the default value of the plugin platform metaField configuration.", + 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); + }, + }, + "checks.pattern": { + title: "Index pattern", + description: "Enable or disable the 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); + }, + }, + "checks.setup": { + title: "API version", + description: "Enable or disable the setup 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); + }, + }, + "checks.template": { + title: "Index template", + description: "Enable or disable the template 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); + }, + }, + "checks.timeFilter": { + title: "Set time filter to 24h", + description: "Change the default value of the plugin platform timeFilter configuration.", + 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); + }, + }, + "cron.prefix": { + title: "Cron prefix", + description: "Define the index prefix of predefined jobs.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: WAZUH_STATISTICS_DEFAULT_PREFIX, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "cron.statistics.apis": { + title: "Includes APIs", + description: "Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.", + category: SettingCategory.STATISTICS, + 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; + }; + }, + }, + "cron.statistics.index.creation": { + title: "Index creation", + description: "Define the interval in which a new index will be created.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.select, + options: { + select: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + defaultValue: WAZUH_STATISTICS_DEFAULT_CREATION, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "cron.statistics.index.name": { + title: "Index name", + description: "Define the name of the index in which the documents will be saved.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.text, + defaultValue: WAZUH_STATISTICS_DEFAULT_NAME, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "cron.statistics.index.replicas": { + title: "Index replicas", + description: "Define the number of replicas to use for the statistics indices.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.number, + defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + options: { + number: { + min: 0 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number): string { + return String(value); + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + }, + "cron.statistics.index.shards": { + title: "Index shards", + description: "Define the number of shards to use for the statistics indices.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.number, + defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + options: { + number: { + min: 1 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number) { + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + }, + "cron.statistics.interval": { + title: "Interval", + description: "Define the frequency of task execution using cron schedule expressions.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.text, + defaultValue: WAZUH_STATISTICS_DEFAULT_CRON_FREQ, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + }, + "cron.statistics.status": { + title: "Status", + description: "Enable or disable the statistics tasks.", + category: SettingCategory.STATISTICS, + type: EpluginSettingType.switch, + defaultValue: WAZUH_STATISTICS_DEFAULT_STATUS, + 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); + }, + }, + "customization.logo.app": { + title: "App main logo", + description: `This logo is used in the app main menu, at the top left corner.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "customization.logo.healthcheck": { + title: "Healthcheck logo", + description: `This logo is displayed during the Healthcheck routine of the app.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "customization.logo.reports": { + title: "PDF reports logo", + description: `This logo is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, + category: SettingCategory.CUSTOMIZATION, + type: EpluginSettingType.text, + defaultValue: "", + defaultValueIfNotSet: REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "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.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresReloadingBrowserTab: true, + }, + "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; + }; + }, + }, + "enrollment.dns": { + title: "Enrollment DNS", + description: "Specifies the Wazuh registration server, used for the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + }, + "enrollment.password": { + title: "Enrollment password", + description: "Specifies the password used to authenticate during the agent enrollment.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: "", + isConfigurableFromFile: true, + isConfigurableFromUI: false, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "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); + }, + }, + "hideManagerAlerts": { + title: "Hide manager alerts", + description: "Hide the alerts of the manager in every dashboard.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.switch, + defaultValue: false, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresReloadingBrowserTab: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: function (value: boolean | string): boolean { + return Boolean(value); + }, + }, + "ip.ignore": { + title: "Index pattern ignore", + description: "Disable certain index pattern names from being available in index pattern selector.", + category: SettingCategory.GENERAL, + 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; + }; + }, + }, + "ip.selector": { + title: "IP selector", + description: "Define if the user is allowed to change the selected index pattern directly from the top menu bar.", + category: SettingCategory.GENERAL, + 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); + }, + }, + "logs.level": { + title: "Log level", + description: "Logging level of the App.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.select, + options: { + select: [ + { + text: "Info", + value: "info" + }, + { + text: "Debug", + value: "debug" + } + ] + }, + defaultValue: "info", + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + }, + "pattern": { + title: "Index pattern", + description: "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.text, + defaultValue: WAZUH_ALERTS_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "timeout": { + title: "Request timeout", + description: "Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.", + category: SettingCategory.GENERAL, + type: EpluginSettingType.number, + defaultValue: 20000, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + options: { + number: { + min: 1500 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number) { + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + }, + "wazuh.monitoring.creation": { + title: "Index creation", + description: "Define the interval in which a new wazuh-monitoring index will be created.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.select, + options: { + select: [ + { + text: "Hourly", + value: "h" + }, + { + text: "Daily", + value: "d" + }, + { + text: "Weekly", + value: "w" + }, + { + text: "Monthly", + value: "m" + } + ] + }, + defaultValue: WAZUH_MONITORING_DEFAULT_CREATION, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "wazuh.monitoring.enabled": { + title: "Status", + description: "Enable or disable the wazuh-monitoring index creation and/or visualization.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.switch, + defaultValue: WAZUH_MONITORING_DEFAULT_ENABLED, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + options: { + switch: { + values: { + disabled: { label: 'false', value: false }, + enabled: { label: 'true', value: true }, + } + } + }, + uiFormTransformChangedInputValue: function (value: boolean | string): boolean { + return Boolean(value); + }, + }, + "wazuh.monitoring.frequency": { + title: "Frequency", + description: "Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.number, + defaultValue: WAZUH_MONITORING_DEFAULT_FREQUENCY, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRestartingPluginPlatform: true, + options: { + number: { + min: 60 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number) { + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + }, + "wazuh.monitoring.pattern": { + title: "Index pattern", + description: "Default index pattern to use for Wazuh monitoring.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.text, + defaultValue: WAZUH_MONITORING_PATTERN, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + }, + "wazuh.monitoring.replicas": { + title: "Index replicas", + description: "Define the number of replicas to use for the wazuh-monitoring-* indices.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.number, + defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + options: { + number: { + min: 0 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number) { + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + }, + "wazuh.monitoring.shards": { + title: "Index shards", + description: "Define the number of shards to use for the wazuh-monitoring-* indices.", + category: SettingCategory.MONITORING, + type: EpluginSettingType.number, + defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, + isConfigurableFromFile: true, + isConfigurableFromUI: true, + requiresRunningHealthCheck: true, + options: { + number: { + min: 1 + } + }, + uiFormTransformConfigurationValueToInputValue: function (value: number) { + return String(value) + }, + uiFormTransformInputValueToConfigurationValue: function (value: string): number { + return Number(value); + }, + } +}; + +export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS; + export enum HTTP_STATUS_CODES { CONTINUE = 100, diff --git a/common/services/settings.test.ts b/common/services/settings.test.ts new file mode 100644 index 0000000000..474236e172 --- /dev/null +++ b/common/services/settings.test.ts @@ -0,0 +1,34 @@ +import { + formatLabelValuePair, + formatSettingValueToFile +} from "./settings"; + +describe('[settings] Methods', () => { + + describe('formatLabelValuePair: Format the label-value pairs used to display the allowed values', () => { + it.each` + label | value | expected + ${'TestLabel'} | ${true} | ${'true (TestLabel)'} + ${'true'} | ${true} | ${'true'} + `(`label: $label | value: $value | expected: $expected`, ({ label, expected, value }) => { + expect(formatLabelValuePair(label, value)).toBe(expected); + }); + }); + + describe('formatSettingValueToFile: Format setting values to save in the configuration file', () => { + it.each` + input | expected + ${'test'} | ${'\"test\"'} + ${'test space'} | ${'\"test space\"'} + ${'test\nnew line'} | ${'\"test\\nnew line\"'} + ${''} | ${'\"\"'} + ${1} | ${1} + ${true} | ${true} + ${false} | ${false} + ${['test1']} | ${'[\"test1\"]'} + ${['test1', 'test2']} | ${'[\"test1\",\"test2\"]'} + `(`input: $input | expected: $expected`, ({ input, expected }) => { + expect(formatSettingValueToFile(input)).toBe(expected); + }); + }); +}); diff --git a/common/services/settings.ts b/common/services/settings.ts new file mode 100644 index 0000000000..4e88c6c5fe --- /dev/null +++ b/common/services/settings.ts @@ -0,0 +1,124 @@ +import { + PLUGIN_SETTINGS, + PLUGIN_SETTINGS_CATEGORIES, + TPluginSetting, + TPluginSettingKey, + TPluginSettingWithKey +} from '../constants'; + +/** + * Look for a configuration category setting by its name + * @param categoryTitle + * @returns category settings + */ +export function getCategorySettingByTitle(categoryTitle: string): any { + return Object.entries(PLUGIN_SETTINGS_CATEGORIES).find(([key, category]) => category?.title == categoryTitle)?.[1]; +} + +/** + * Get the default value of the plugin setting. + * @param setting setting key + * @returns setting default value. It returns `defaultValueIfNotSet` or `defaultValue`. + */ +export function getSettingDefaultValue(settingKey: string): any { + return typeof PLUGIN_SETTINGS[settingKey].defaultValueIfNotSet !== 'undefined' + ? PLUGIN_SETTINGS[settingKey].defaultValueIfNotSet + : PLUGIN_SETTINGS[settingKey].defaultValue; +}; + +/** + * Get the default settings configuration. key-value pair + * @returns an object with key-value pairs whose value is the default one + */ +export function getSettingsDefault() : {[key in TPluginSettingKey]: unknown} { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ + ...accum, + [pluginSettingID]: pluginSettingConfiguration.defaultValue + }), {}); +}; + +/** + * Get the settings grouped by category + * @returns an object whose keys are the categories and its value is an array of setting of that category + */ +export function getSettingsByCategories() : {[key: string]: TPluginSetting[]} { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ + ...accum, + [pluginSettingConfiguration.category]: [...(accum[pluginSettingConfiguration.category] || []), { ...pluginSettingConfiguration, key: pluginSettingID }] + }), {}); +}; + +/** + * Get the plugin settings as an array + * @returns an array of plugin setting denifitions including the key + */ +export function getSettingsDefaultList(): TPluginSettingWithKey[] { + return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ([ + ...accum, + { ...pluginSettingConfiguration, key: pluginSettingID } + ]), []); +}; + +/** + * Format the plugin setting value received in the backend to store in the plugin configuration file (.yml). + * @param value plugin setting value sent to the endpoint + * @returns valid value to .yml + */ +export function formatSettingValueToFile(value: any) { + const formatter = formatSettingValueToFileType[typeof value] || formatSettingValueToFileType.default; + return formatter(value); +}; + +const formatSettingValueToFileType = { + string: (value: string): string => `"${value.replace(/"/,'\\"').replace(/\n/g,'\\n')}"`, // Escape the " character and new line + object: (value: any): string => JSON.stringify(value), + default: (value: any): any => value +}; + +/** + * Group the settings by category + * @param settings + * @returns + */ +export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ + const settingsSortedByCategories = settings + .sort((settingA, settingB) => settingA.key?.localeCompare?.(settingB.key)) + .reduce((accum, pluginSettingConfiguration) => ({ + ...accum, + [pluginSettingConfiguration.category]: [ + ...(accum[pluginSettingConfiguration.category] || []), + { ...pluginSettingConfiguration } + ] + }), {}); + + return Object.entries(settingsSortedByCategories) + .map(([category, settings]) => ({ category, settings })) + .filter(categoryEntry => categoryEntry.settings.length); +}; + +/** + * Get the plugin setting description composed. + * @param options + * @returns + */ + export function getPluginSettingDescription({description, options}: TPluginSetting): string{ + return [ + description, + ...(options?.select ? [`Allowed values: ${options.select.map(({text, value}) => formatLabelValuePair(text, value)).join(', ')}.`] : []), + ...(options?.switch ? [`Allowed values: ${['enabled', 'disabled'].map(s => formatLabelValuePair(options.switch.values[s].label, options.switch.values[s].value)).join(', ')}.`] : []), + ...(options?.number && 'min' in options.number ? [`Minimum value: ${options.number.min}.`] : []), + ...(options?.number && 'max' in options.number ? [`Maximum value: ${options.number.max}.`] : []), + ].join(' '); +}; + +/** + * Format the pair value-label to display the pair. If label and the string of value are equals, only displays the value, if not, displays both. + * @param value + * @param label + * @returns + */ +export function formatLabelValuePair(label, value){ + return label !== `${value}` + ? `${value} (${label})` + : `${value}` +}; diff --git a/public/components/common/form/__snapshots__/index.test.tsx.snap b/public/components/common/form/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..7be48e84ec --- /dev/null +++ b/public/components/common/form/__snapshots__/index.test.tsx.snap @@ -0,0 +1,253 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`[component] InputForm Renders correctly to match the snapshot: Input: editor 1`] = ` +
+
+ +
+