diff --git a/common/config-equivalences.js b/common/config-equivalences.js
deleted file mode 100644
index 0441e3f8b1..0000000000
--- a/common/config-equivalences.js
+++ /dev/null
@@ -1,234 +0,0 @@
-import { ASSETS_PUBLIC_URL, PLUGIN_PLATFORM_NAME } from "./constants";
-
-export const configEquivalences = {
- pattern: "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.",
- 'customization.logo.app':`Set the name of the app logo stored at ${ASSETS_PUBLIC_URL}`,
- 'customization.logo.sidebar':`Set the name of the sidebar logo stored at ${ASSETS_PUBLIC_URL}`,
- 'customization.logo.healthcheck':`Set the name of the health-check logo stored at ${ASSETS_PUBLIC_URL}`,
- 'customization.logo.reports':`Set the name of the reports logo (.png) stored at ${ASSETS_PUBLIC_URL}`,
- 'checks.pattern':
- 'Enable or disable the index pattern health check when opening the app.',
- 'checks.template':
- 'Enable or disable the template health check when opening the app.',
- 'checks.api': 'Enable or disable the API health check when opening the app.',
- 'checks.setup':
- 'Enable or disable the setup health check when opening the app.',
- 'checks.fields':
- 'Enable or disable the known fields health check when opening the app.',
- 'checks.metaFields':
- `Change the default value of the ${PLUGIN_PLATFORM_NAME} metaField configuration`,
- 'checks.timeFilter':
- `Change the default value of the ${PLUGIN_PLATFORM_NAME} timeFilter configuration`,
- 'checks.maxBuckets':
- `Change the default value of the ${PLUGIN_PLATFORM_NAME} max buckets configuration`,
- 'extensions.pci': 'Enable or disable the PCI DSS tab on Overview and Agents.',
- 'extensions.gdpr': 'Enable or disable the GDPR tab on Overview and Agents.',
- 'extensions.hipaa': 'Enable or disable the HIPAA tab on Overview and Agents.',
- 'extensions.nist': 'Enable or disable the NIST 800-53 tab on Overview and Agents.',
- 'extensions.tsc': 'Enable or disable the TSC tab on Overview and Agents.',
- 'extensions.audit': 'Enable or disable the Audit tab on Overview and Agents.',
- 'extensions.oscap':
- 'Enable or disable the Open SCAP tab on Overview and Agents.',
- 'extensions.ciscat':
- 'Enable or disable the CIS-CAT tab on Overview and Agents.',
- 'extensions.aws': 'Enable or disable the Amazon (AWS) tab on Overview.',
- 'extensions.gcp': 'Enable or disable the Google Cloud Platform tab on Overview.',
- 'extensions.virustotal':
- 'Enable or disable the VirusTotal tab on Overview and Agents.',
- 'extensions.osquery':
- 'Enable or disable the Osquery tab on Overview and Agents.',
- 'extensions.mitre': 'Enable or disable the MITRE tab on Overview and Agents.',
- 'extensions.docker':
- 'Enable or disable the Docker listener tab on Overview and Agents.',
- timeout:
- 'Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.',
- 'ip.selector':
- 'Define if the user is allowed to change the selected index pattern directly from the top menu bar.',
- 'ip.ignore':
- 'Disable certain index pattern names from being available in index pattern selector from the Wazuh app.',
- 'wazuh.monitoring.enabled':
- 'Enable or disable the wazuh-monitoring index creation and/or visualization.',
- 'wazuh.monitoring.frequency':
- 'Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.',
- 'wazuh.monitoring.shards':
- 'Define the number of shards to use for the wazuh-monitoring-* indices.',
- 'wazuh.monitoring.replicas':
- 'Define the number of replicas to use for the wazuh-monitoring-* indices.',
- 'wazuh.monitoring.creation':
- 'Define the interval in which a new wazuh-monitoring index will be created.',
- 'wazuh.monitoring.pattern':
- 'Default index pattern to use for Wazuh monitoring.',
- hideManagerAlerts:
- 'Hide the alerts of the manager in every dashboard.',
- 'logs.level':
- 'Logging level of the App.',
- 'enrollment.dns':
- 'Specifies the Wazuh registration server, used for the agent enrollment.',
- 'enrollment.password':
- 'Specifies the password used to authenticate during the agent enrollment.',
- 'cron.prefix':
- 'Define the index prefix of predefined jobs.',
- 'cron.statistics.status':
- 'Enable or disable the statistics tasks.',
- 'cron.statistics.apis':
- 'Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.',
- 'cron.statistics.interval': 'Define the frequency of task execution using cron schedule expressions.',
- 'cron.statistics.index.name': 'Define the name of the index in which the documents will be saved.',
- 'cron.statistics.index.creation': 'Define the interval in which a new index will be created.',
- 'cron.statistics.index.shards': 'Define the number of shards to use for the statistics indices.',
- 'cron.statistics.index.replicas': 'Define the number of replicas to use for the statistics indices.',
- 'alerts.sample.prefix': 'Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.',
-};
-
-export const nameEquivalence = {
- pattern: 'Index pattern',
- 'customization.logo.app': 'Logo App',
- 'customization.logo.sidebar': 'Logo Sidebar',
- 'customization.logo.healthcheck': 'Logo Health Check',
- 'customization.logo.reports': 'Logo Reports',
- 'checks.pattern': 'Index pattern',
- 'checks.template': 'Index template',
- 'checks.api': 'API connection',
- 'checks.setup': 'API version',
- 'checks.fields': 'Known fields',
- 'checks.metaFields': 'Remove meta fields',
- 'checks.timeFilter': 'Set time filter to 24h',
- 'checks.maxBuckets': 'Set max buckets to 200000',
- timeout: 'Request timeout',
- 'ip.selector': 'IP selector',
- 'ip.ignore': 'IP ignore',
- 'xpack.rbac.enabled': 'X-Pack RBAC',
- 'wazuh.monitoring.enabled': 'Status',
- 'wazuh.monitoring.frequency': 'Frequency',
- 'wazuh.monitoring.shards': 'Index shards',
- 'wazuh.monitoring.replicas': 'Index replicas',
- 'wazuh.monitoring.creation': 'Index creation',
- 'wazuh.monitoring.pattern': 'Index pattern',
- hideManagerAlerts: 'Hide manager alerts',
- 'logs.level': 'Log level',
- 'enrollment.dns': 'Enrollment DNS',
- 'cron.prefix': 'Cron prefix',
- 'cron.statistics.status': 'Status',
- 'cron.statistics.apis': 'Includes apis',
- 'cron.statistics.interval': 'Interval',
- 'cron.statistics.index.name': 'Index name',
- 'cron.statistics.index.creation': 'Index creation',
- 'cron.statistics.index.shards': 'Index shards',
- 'cron.statistics.index.replicas': 'Index replicas',
- 'alerts.sample.prefix': 'Sample alerts prefix',
-}
-
-const HEALTH_CHECK = 'Health Check';
-const GENERAL = 'General';
-const SECURITY = 'Security';
-const MONITORING = 'Monitoring';
-const STATISTICS = 'Statistics';
-const CUSTOMIZATION = 'Logo Customization';
-export const categoriesNames = [HEALTH_CHECK, GENERAL, SECURITY, MONITORING, STATISTICS, CUSTOMIZATION];
-
-export const categoriesEquivalence = {
- pattern: GENERAL,
- 'customization.logo.app':CUSTOMIZATION,
- 'customization.logo.sidebar':CUSTOMIZATION,
- 'customization.logo.healthcheck':CUSTOMIZATION,
- 'customization.logo.reports':CUSTOMIZATION,
- 'checks.pattern': HEALTH_CHECK,
- 'checks.template': HEALTH_CHECK,
- 'checks.api': HEALTH_CHECK,
- 'checks.setup': HEALTH_CHECK,
- 'checks.fields': HEALTH_CHECK,
- 'checks.metaFields': HEALTH_CHECK,
- 'checks.timeFilter': HEALTH_CHECK,
- 'checks.maxBuckets': HEALTH_CHECK,
- timeout: GENERAL,
- 'ip.selector': GENERAL,
- 'ip.ignore': GENERAL,
- 'wazuh.monitoring.enabled': MONITORING,
- 'wazuh.monitoring.frequency': MONITORING,
- 'wazuh.monitoring.shards': MONITORING,
- 'wazuh.monitoring.replicas': MONITORING,
- 'wazuh.monitoring.creation': MONITORING,
- 'wazuh.monitoring.pattern': MONITORING,
- hideManagerAlerts: GENERAL,
- 'logs.level': GENERAL,
- 'enrollment.dns': GENERAL,
- 'cron.prefix': GENERAL,
- 'cron.statistics.status': STATISTICS,
- 'cron.statistics.apis': STATISTICS,
- 'cron.statistics.interval': STATISTICS,
- 'cron.statistics.index.name': STATISTICS,
- 'cron.statistics.index.creation': STATISTICS,
- 'cron.statistics.index.shards': STATISTICS,
- 'cron.statistics.index.replicas': STATISTICS,
- 'alerts.sample.prefix': GENERAL,
-}
-
-const TEXT = 'text';
-const NUMBER = 'number';
-const LIST = 'list';
-const BOOLEAN = 'boolean';
-const ARRAY = 'array';
-const INTERVAL = 'interval'
-
-export const formEquivalence = {
- pattern: { type: TEXT },
- 'customization.logo.app': { type: TEXT },
- 'customization.logo.sidebar': { type: TEXT },
- 'customization.logo.healthcheck': { type: TEXT },
- 'customization.logo.reports': { type: TEXT },
- 'checks.pattern': { type: BOOLEAN },
- 'checks.template': { type: BOOLEAN },
- 'checks.api': { type: BOOLEAN },
- 'checks.setup': { type: BOOLEAN },
- 'checks.fields': { type: BOOLEAN },
- 'checks.metaFields': { type: BOOLEAN },
- 'checks.timeFilter': { type: BOOLEAN },
- 'checks.maxBuckets': { type: BOOLEAN },
- timeout: { type: NUMBER },
- 'ip.selector': { type: BOOLEAN },
- 'ip.ignore': { type: ARRAY },
- 'xpack.rbac.enabled': { type: BOOLEAN },
- 'wazuh.monitoring.enabled': { type: BOOLEAN },
- 'wazuh.monitoring.frequency': { type: NUMBER },
- 'wazuh.monitoring.shards': { type: NUMBER },
- 'wazuh.monitoring.replicas': { type: NUMBER },
- 'wazuh.monitoring.creation': {
- type: LIST, params: {
- options: [
- { text: 'Hourly', value: 'h' },
- { text: 'Daily', value: 'd' },
- { text: 'Weekly', value: 'w' },
- { text: 'Monthly', value: 'm' },
- ]
- }
- },
- 'wazuh.monitoring.pattern': { type: TEXT },
- hideManagerAlerts: { type: BOOLEAN },
- 'logs.level': {
- type: LIST, params: {
- options: [
- { text: 'Info', value: 'info' },
- { text: 'Debug', value: 'debug' },
- ]
- }
- },
- 'enrollment.dns': { type: TEXT },
- 'cron.prefix': { type: TEXT },
- 'cron.statistics.status': { type: BOOLEAN },
- 'cron.statistics.apis': { type: ARRAY },
- 'cron.statistics.interval': { type: INTERVAL },
- 'cron.statistics.index.name': { type: TEXT },
- 'cron.statistics.index.creation': {
- type: LIST, params: {
- options: [
- { text: 'Hourly', value: 'h' },
- { text: 'Daily', value: 'd' },
- { text: 'Weekly', value: 'w' },
- { text: 'Monthly', value: 'm' },
- ]
- }
- },
- 'cron.statistics.index.shards': { type: NUMBER },
- 'cron.statistics.index.replicas': { type: NUMBER },
- 'alerts.sample.prefix': { type: TEXT },
-}
diff --git a/common/constants.ts b/common/constants.ts
index f323c32a4c..f0e1657030 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';
@@ -100,28 +100,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;
@@ -176,61 +154,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': [],
- 'xpack.rbac.enabled': true,
- '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';
@@ -417,6 +340,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..88693cee2d
--- /dev/null
+++ b/public/components/common/form/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,255 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`[component] InputForm Renders correctly to match the snapshot: Input: editor 1`] = `
+
+
+
+
+ Press Enter to start editing.
+
+
+ When you're done, press Escape to stop editing.
+
+
+
+
+
+
+
+
+
+
+
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+
+
+
+
+
+`;
+
+exports[`[component] InputForm Renders correctly to match the snapshot: Input: number 1`] = `
+
+`;
+
+exports[`[component] InputForm Renders correctly to match the snapshot: Input: select 1`] = `
+
+`;
+
+exports[`[component] InputForm Renders correctly to match the snapshot: Input: switch 1`] = `
+
+
+
+
+ Enabled
+
+
+
+`;
+
+exports[`[component] InputForm Renders correctly to match the snapshot: Input: text 1`] = `
+
+`;
diff --git a/public/components/common/form/hooks.test.tsx b/public/components/common/form/hooks.test.tsx
new file mode 100644
index 0000000000..6d9a63fd21
--- /dev/null
+++ b/public/components/common/form/hooks.test.tsx
@@ -0,0 +1,137 @@
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useForm } from './hooks';
+
+describe('[hook] useForm', () => {
+
+ it(`[hook] useForm. Verify the initial state`, async () => {
+
+ const initialFields = {
+ text1: {
+ type: 'text',
+ initialValue: ''
+ },
+ };
+
+ const { result } = renderHook(() => useForm(initialFields));
+
+ // assert initial state
+ expect(result.current.fields.text1.changed).toBe(false);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe('text');
+ expect(result.current.fields.text1.value).toBe('');
+ expect(result.current.fields.text1.initialValue).toBe('');
+ expect(result.current.fields.text1.onChange).toBeDefined();
+ });
+
+ it(`[hook] useForm. Verify the initial state. Multiple fields.`, async () => {
+
+ const initialFields = {
+ text1: {
+ type: 'text',
+ initialValue: ''
+ },
+ number1: {
+ type: 'number',
+ initialValue: 1
+ },
+ };
+
+ const { result } = renderHook(() => useForm(initialFields));
+
+ // assert initial state
+ expect(result.current.fields.text1.changed).toBe(false);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe('text');
+ expect(result.current.fields.text1.value).toBe('');
+ expect(result.current.fields.text1.initialValue).toBe('');
+ expect(result.current.fields.text1.onChange).toBeDefined();
+
+ expect(result.current.fields.number1.changed).toBe(false);
+ expect(result.current.fields.number1.error).toBeUndefined();
+ expect(result.current.fields.number1.type).toBe('number');
+ expect(result.current.fields.number1.value).toBe(1);
+ expect(result.current.fields.number1.initialValue).toBe(1);
+ expect(result.current.fields.number1.onChange).toBeDefined();
+ });
+
+ it(`[hook] useForm lifecycle. Set the initial value. Change the field value. Undo changes. Change the field. Do changes.`, async () => {
+
+ const initialFieldValue = '';
+ const fieldType = 'text';
+
+ const initialFields = {
+ text1: {
+ type: fieldType,
+ initialValue: initialFieldValue
+ }
+ };
+
+ const { result } = renderHook(() => useForm(initialFields));
+
+ // assert initial state
+ expect(result.current.fields.text1.changed).toBe(false);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe(fieldType);
+ expect(result.current.fields.text1.value).toBe(initialFieldValue);
+ expect(result.current.fields.text1.initialValue).toBe(initialFieldValue);
+ expect(result.current.fields.text1.onChange).toBeDefined();
+
+ // change the input
+ const changedValue = 't';
+ act(() => {
+ result.current.fields.text1.onChange({
+ target: {
+ value: changedValue
+ }
+ });
+ });
+
+ // assert changed state
+ expect(result.current.fields.text1.changed).toBe(true);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe(fieldType);
+ expect(result.current.fields.text1.value).toBe(changedValue);
+ expect(result.current.fields.text1.initialValue).toBe(initialFieldValue);
+
+ // undone changes
+ act(() => {
+ result.current.undoChanges();
+ });
+
+ // assert undo changes state
+ expect(result.current.fields.text1.changed).toBe(false);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe(fieldType);
+ expect(result.current.fields.text1.value).toBe(initialFieldValue);
+ expect(result.current.fields.text1.initialValue).toBe(initialFieldValue);
+
+ // change the input
+ const changedValue2 = 'e';
+ act(() => {
+ result.current.fields.text1.onChange({
+ target: {
+ value: changedValue2
+ }
+ });
+ });
+
+ // assert changed state
+ expect(result.current.fields.text1.changed).toBe(true);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe(fieldType);
+ expect(result.current.fields.text1.value).toBe(changedValue2);
+ expect(result.current.fields.text1.initialValue).toBe(initialFieldValue);
+
+ // done changes
+ act(() => {
+ result.current.doChanges()
+ });
+
+ // assert do changes state
+ expect(result.current.fields.text1.changed).toBe(false);
+ expect(result.current.fields.text1.error).toBeUndefined();
+ expect(result.current.fields.text1.type).toBe(fieldType);
+ expect(result.current.fields.text1.value).toBe(changedValue2);
+ expect(result.current.fields.text1.initialValue).toBe(changedValue2);
+ });
+});
diff --git a/public/components/common/form/hooks.tsx b/public/components/common/form/hooks.tsx
new file mode 100644
index 0000000000..1778f147d7
--- /dev/null
+++ b/public/components/common/form/hooks.tsx
@@ -0,0 +1,86 @@
+import { useState } from 'react';
+import { isEqual } from 'lodash';
+import { EpluginSettingType } from '../../../../common/constants';
+
+function getValueFromEvent(event, type){
+ return getValueFromEventType?.[type]?.(event) || getValueFromEventType.default(event)
+};
+
+const getValueFromEventType = {
+ [EpluginSettingType.switch] : (event: any) => event.target.checked,
+ [EpluginSettingType.editor]: (value: any) => value,
+ default: (event: any) => event.target.value,
+};
+
+export const useForm = (fields) => {
+ const [formFields, setFormFields] = useState(Object.entries(fields).reduce((accum, [fieldKey, fieldConfiguration]) => ({
+ ...accum,
+ [fieldKey]: {
+ currentValue: fieldConfiguration.initialValue,
+ initialValue: fieldConfiguration.initialValue,
+ }
+ }), {}));
+
+ const enhanceFields = Object.entries(formFields).reduce((accum, [fieldKey, {currentValue: value, ...restFieldState}]) => ({
+ ...accum,
+ [fieldKey]: {
+ ...fields[fieldKey],
+ ...restFieldState,
+ type: fields[fieldKey].type,
+ value,
+ changed: !isEqual(restFieldState.initialValue, value),
+ error: fields[fieldKey]?.validate?.(restFieldState.currentValue),
+ onChange: (event) => {
+ const inputValue = getValueFromEvent(event, fields[fieldKey].type);
+ const currentValue = fields[fieldKey]?.transformChangedInputValue?.(inputValue) ?? inputValue;
+ setFormFields(state => ({
+ ...state,
+ [fieldKey]: {
+ ...state[fieldKey],
+ currentValue,
+ }
+ }))
+ },
+ }
+ }), {});
+
+ const changed = Object.fromEntries(
+ Object.entries(enhanceFields).filter(([, {changed}]) => changed).map(([fieldKey, {value}]) => ([fieldKey, fields[fieldKey]?.transformChangedOutputValue?.(value) ?? value]))
+ );
+
+ const errors = Object.fromEntries(
+ Object.entries(enhanceFields).filter(([, {error}]) => error).map(([fieldKey, {error}]) => ([fieldKey, error]))
+ );
+
+ function undoChanges(){
+ setFormFields(state => Object.fromEntries(
+ Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([
+ fieldKey,
+ {
+ ...fieldConfiguration,
+ currentValue: fieldConfiguration.initialValue
+ }
+ ]))
+ ));
+ };
+
+ function doChanges(){
+ setFormFields(state => Object.fromEntries(
+ Object.entries(state).map(([fieldKey, fieldConfiguration]) => ([
+ fieldKey,
+ {
+ ...fieldConfiguration,
+ initialValue: fieldConfiguration.currentValue
+ }
+ ]))
+ ));
+ };
+
+ return {
+ fields: enhanceFields,
+ changed,
+ errors,
+ undoChanges,
+ doChanges
+ };
+};
diff --git a/public/components/common/form/index.test.tsx b/public/components/common/form/index.test.tsx
new file mode 100644
index 0000000000..f982571cf7
--- /dev/null
+++ b/public/components/common/form/index.test.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { InputForm } from './index';
+
+jest.mock('../../../../../../node_modules/@elastic/eui/lib/services/accessibility', () => ({
+ htmlIdGenerator: () => () => 'generated-id',
+}));
+
+describe('[component] InputForm', () => {
+ const optionsEditor = {editor: {language: 'json'}};
+ const optionsSelect = {select: [{text: 'Label1', value: 'value1'}, {text: 'Label2', value: 'value2'}]};
+ const optionsSwitch = {switch: {values: {enabled: {label: 'Enabled', value: true}, disabled: {label: 'Disabled', value: false}}}};
+ it.each`
+ inputType | value | options
+ ${'editor'} | ${'{}'} | ${optionsEditor}
+ ${'number'} | ${4} | ${undefined}
+ ${'select'} | ${'value1'} | ${optionsSelect}
+ ${'switch'} | ${true} | ${optionsSwitch}
+ ${'text'} | ${'test'} | ${undefined}
+ `('Renders correctly to match the snapshot: Input: $inputType', ({ inputType, value, options }) => {
+ const wrapper = render(
+ {}}
+ options={options}
+ />
+ );
+ expect(wrapper.container).toMatchSnapshot();
+ });
+});
+
diff --git a/public/components/common/form/index.tsx b/public/components/common/form/index.tsx
new file mode 100644
index 0000000000..62d8e15080
--- /dev/null
+++ b/public/components/common/form/index.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { InputFormEditor } from './input_editor';
+import { InputFormNumber } from './input_number';
+import { InputFormText } from './input_text';
+import { InputFormSwitch } from './input_switch';
+import { InputFormSelect } from './input_select';
+import { EuiFormRow } from '@elastic/eui';
+
+export const InputForm = ({ type, value, onChange, error, label, preInput, postInput, ...rest}) => {
+
+ const ComponentInput = Input[type];
+
+ if(!ComponentInput){
+ return null;
+ };
+
+ const isInvalid = Boolean(error);
+
+ const input = (
+
+ );
+
+ return label
+ ? (
+
+ <>
+ {typeof preInput === 'function' ? preInput({value, error}) : preInput}
+ {input}
+ {typeof postInput === 'function' ? postInput({value, error}) : postInput}
+ >
+ )
+ : input;
+
+};
+
+const Input = {
+ switch: InputFormSwitch,
+ editor: InputFormEditor,
+ number: InputFormNumber,
+ select: InputFormSelect,
+ text: InputFormText,
+};
diff --git a/public/components/common/form/input_editor.tsx b/public/components/common/form/input_editor.tsx
new file mode 100644
index 0000000000..ab38c89f6d
--- /dev/null
+++ b/public/components/common/form/input_editor.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { EuiCodeEditor } from '@elastic/eui';
+import { IInputFormType } from './types';
+
+export const InputFormEditor = ({options, value, onChange}: IInputFormType) => {
+ return (
+
+ );
+};
diff --git a/public/components/common/form/input_number.tsx b/public/components/common/form/input_number.tsx
new file mode 100644
index 0000000000..acc21002f4
--- /dev/null
+++ b/public/components/common/form/input_number.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { EuiFieldNumber } from '@elastic/eui';
+import { IInputFormType } from './types';
+
+export const InputFormNumber = ({ options, value, onChange }: IInputFormType) => {
+ return (
+
+ );
+};
diff --git a/public/components/common/form/input_select.tsx b/public/components/common/form/input_select.tsx
new file mode 100644
index 0000000000..a8f02e99d7
--- /dev/null
+++ b/public/components/common/form/input_select.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import { EuiSelect } from '@elastic/eui';
+import { IInputFormType } from './types';
+
+export const InputFormSelect = ({ options, value, onChange }: IInputFormType) => {
+ return (
+
+ )
+};
diff --git a/public/components/common/form/input_switch.tsx b/public/components/common/form/input_switch.tsx
new file mode 100644
index 0000000000..75a575d97f
--- /dev/null
+++ b/public/components/common/form/input_switch.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { EuiSwitch } from '@elastic/eui';
+import { IInputFormType } from './types';
+
+export const InputFormSwitch = ({ options, value, onChange }: IInputFormType) => {
+ const checked = Object.entries(options.switch.values)
+ .find(([, { value: statusValue }]) => value === statusValue)[0];
+
+ return (
+
+ );
+};
diff --git a/public/components/common/form/input_text.tsx b/public/components/common/form/input_text.tsx
new file mode 100644
index 0000000000..feb0d218ee
--- /dev/null
+++ b/public/components/common/form/input_text.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { EuiFieldText } from '@elastic/eui';
+import { IInputFormType } from "./types";
+
+export const InputFormText = ({ value, isInvalid, onChange }: IInputFormType) => {
+ return (
+
+ );
+};
diff --git a/public/components/common/form/types.ts b/public/components/common/form/types.ts
new file mode 100644
index 0000000000..b1d8c51120
--- /dev/null
+++ b/public/components/common/form/types.ts
@@ -0,0 +1,18 @@
+import { TPluginSettingWithKey } from "../../../../common/constants";
+
+export interface IInputFormType {
+ field: TPluginSettingWithKey
+ value: any
+ onChange: (event: any) => void
+ isInvalid?: boolean
+ options: any
+};
+
+export interface IInputForm {
+ field: TPluginSettingWithKey
+ initialValue: any
+ onChange: (event: any) => void
+ label?: string
+ preInput?: ((options: {value: any, error: string | null}) => JSX.Element)
+ postInput?: ((options: {value: any, error: string | null}) => JSX.Element)
+};
diff --git a/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx b/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
index 43090a7510..b75770a669 100644
--- a/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
+++ b/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
@@ -24,15 +24,13 @@ import {
EuiPopover,
EuiText,
EuiIcon,
- EuiOverlayMask,
- EuiOutsideClickDetector,
EuiLoadingSpinner,
} from '@elastic/eui';
import { AppNavigate } from '../../../../../react-services/app-navigate';
import { AppState } from '../../../../../react-services/app-state';
import { RequirementFlyout } from '../requirement-flyout/requirement-flyout';
-import { WAZUH_ALERTS_PATTERN } from '../../../../../../common/constants';
import { getDataPlugin } from '../../../../../kibana-services';
+import { getSettingDefaultValue } from '../../../../../../common/services/settings';
export class ComplianceSubrequirements extends Component {
_isMount = false;
@@ -71,7 +69,7 @@ export class ComplianceSubrequirements extends Component {
params: { query: filter.value },
type: 'phrase',
negate: filter.negate || false,
- index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN,
+ index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'),
},
query: { match_phrase: matchPhrase },
$state: { store: 'appState' },
diff --git a/public/components/overview/mitre/components/techniques/techniques.tsx b/public/components/overview/mitre/components/techniques/techniques.tsx
index 8234de0b73..50c4f10b28 100644
--- a/public/components/overview/mitre/components/techniques/techniques.tsx
+++ b/public/components/overview/mitre/components/techniques/techniques.tsx
@@ -9,7 +9,7 @@
*
* Find more information about this on the LICENSE file.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import {
EuiFacetButton,
EuiFlexGroup,
@@ -31,13 +31,13 @@ import { getElasticAlerts, IFilterParams } from '../../lib';
import { ITactic } from '../../';
import { withWindowSize } from '../../../../../components/common/hocs/withWindowSize';
import { WzRequest } from '../../../../../react-services/wz-request';
-import { WAZUH_ALERTS_PATTERN } from '../../../../../../common/constants';
import { AppState } from '../../../../../react-services/app-state';
import { WzFieldSearchDelay } from '../../../../common/search';
import { getDataPlugin, getToasts } from '../../../../../kibana-services';
import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
import { getErrorOrchestrator } from '../../../../../react-services/common-services';
+import { getSettingDefaultValue } from '../../../../../../common/services/settings';
const MITRE_ATTACK = 'mitre-attack';
@@ -430,7 +430,7 @@ export const Techniques = withWindowSize(
params: { query: filter.value },
type: 'phrase',
negate: filter.negate || false,
- index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN,
+ index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'),
},
query: { match_phrase: matchPhrase },
$state: { store: 'appState' },
diff --git a/public/components/settings/configuration/components/bottom-bar.tsx b/public/components/settings/configuration/components/bottom-bar.tsx
index 6ea078f057..bfb44f269f 100644
--- a/public/components/settings/configuration/components/bottom-bar.tsx
+++ b/public/components/settings/configuration/components/bottom-bar.tsx
@@ -11,11 +11,8 @@
* Find more information about this on the LICENSE file.
*/
-import React, { } from 'react';
-import ConfigurationHandler from '../utils/configuration-handler';
-//@ts-ignore
-import { getToasts } from '../../../../kibana-services';
-import { ISetting } from '../configuration'
+import React from 'react';
+
import {
EuiBottomBar,
EuiFlexGroup,
@@ -24,50 +21,34 @@ import {
EuiButtonEmpty,
EuiButton
} from '@elastic/eui';
-import { WazuhConfig } from '../../../../react-services/wazuh-config';
-import { UI_LOGGER_LEVELS, PLUGIN_PLATFORM_NAME } from '../../../../../common/constants';
-import {
- UI_ERROR_SEVERITIES,
- UIErrorLog,
- UIErrorSeverity,
- UILogLevel,
-} from '../../../../react-services/error-orchestrator/types';
-import { getErrorOrchestrator } from '../../../../react-services/common-services';
interface IBottomBarProps {
- updatedConfig: { [setting: string]: string | number | boolean | object }
- setUpdateConfig(setting: {}): void
- setLoading(loading: boolean): void
- config: ISetting[]
+ unsavedCount: number
+ errorsCount: number
+ onCancel: () => void
+ onSave: () => void
}
-export const BottomBar: React.FunctionComponent = ({ updatedConfig, setUpdateConfig, setLoading, config }) => {
- return (!!Object.keys(updatedConfig).length
- ?
+export const BottomBar: React.FunctionComponent = ({ unsavedCount, onCancel, onSave }) => (
+
-
-
-
+
+
+
- : null
- );
-}
+);
-const SettingLabel = ({ updatedConfig }) => (
+const SettingLabel = ({ count, }) => (
- {`${Object.keys(updatedConfig).length} unsaved settings`}
+ {`${count} unsaved settings`}
);
-const CancelButton = ({ setUpdateConfig }) => (
+const CancelButton = ({ onClick }) => (
(
iconType='cross'
color="ghost"
className="mgtAdvancedSettingsForm__button"
- onClick={() => setUpdateConfig({})}>
+ onClick={onClick}>
Cancel changes
-)
+);
-const SaveButton = ({ updatedConfig, setUpdateConfig, setLoading, config }) => (
+const SaveButton = ({ onClick }) => (
(
iconType='check'
color='secondary'
className="mgtAdvancedSettingsForm__button"
- onClick={() => saveSettings(updatedConfig, setUpdateConfig, setLoading, config)} >
+ onClick={onClick} >
Save changes
-)
-
-const saveSettings = async (updatedConfig: {}, setUpdateConfig: Function, setLoading: Function, config: ISetting[]) => {
- setLoading(true);
- try {
- await Promise.all(Object.keys(updatedConfig).map(async setting => await saveSetting(setting, updatedConfig, config)));
- successToast();
- setUpdateConfig({});
- } catch (error) {
- const options: UIErrorLog = {
- context: `${BottomBar.name}.saveSettings`,
- level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
- severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error saving the configuration: ${error.message || error}`,
- },
- };
-
- getErrorOrchestrator().handleError(options);
- } finally {
- setLoading(false);
- }
-}
-
-const saveSetting = async (setting, updatedConfig, config: ISetting[]) => {
- try {
- (config.find(item => item.setting === setting) || { value: '' }).value = updatedConfig[setting];
- const result = await ConfigurationHandler.editKey(setting, updatedConfig[setting]);
-
- // Update the app configuration frontend-cached setting in memory with the new value
- const wzConfig = new WazuhConfig();
- wzConfig.setConfig({ ...wzConfig.getConfig(), ...{ [setting]: formatValueCachedConfiguration(updatedConfig[setting]) } });
-
- // Show restart and/or reload message in toast
- const response = result.data.data;
- response.needRestart && restartToast();
- response.needReload && reloadToast();
- response.needHealtCheck && executeHealtCheck();
- } catch (error) {
- return Promise.reject(error);
- }
-}
-
-const reloadToast = () => {
- getToasts().add({
- color: 'success',
- title: 'This settings require you to reload the page to take effect.',
- text:
-
- window.location.reload()} size="s">Reload page
-
-
- })
-}
-
-const executeHealtCheck = () => {
- const toast = getToasts().add({
- color: 'warning',
- title: 'You must execute the health check for the changes to take effect',
- toastLifeTimeMs: 5000,
- text:
-
-
- {
- getToasts().remove(toast);
- window.location.href = '#/health-check';
- }} size="s">Execute health check
-
-
- });
-}
-
-const restartToast = () => {
- getToasts().add({
- color: 'warning',
- title: `You must restart ${PLUGIN_PLATFORM_NAME} for the changes to take effect`,
- });
-}
-
-const successToast = () => {
- getToasts().add({
- color: 'success',
- title: 'The configuration has been successfully updated',
- });
-}
-
-const formatValueCachedConfiguration = (value) => typeof value === 'string'
- ? isNaN(Number(value)) ? value : Number(value)
- : value;
+);
diff --git a/public/components/settings/configuration/components/categories/categories.tsx b/public/components/settings/configuration/components/categories/categories.tsx
deleted file mode 100644
index b32d8d94ea..0000000000
--- a/public/components/settings/configuration/components/categories/categories.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Wazuh app - React component building the configuration component.
- *
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { } from 'react';
-import { Category } from './components';
-import { ISetting } from '../../configuration';
-import { EuiFlexGroup } from '@elastic/eui';
-
-interface ICategoriesProps {
- config: ISetting[],
- updatedConfig: {[field:string]: string | number | boolean | []}
- setUpdatedConfig({}): void
-}
-
-export const Categories:React.FunctionComponent = ({ config, updatedConfig, setUpdatedConfig }) => {
- const categories: {[category:string]: ISetting[]} = config.reduce((acc, conf) => {
- if (!conf.category) return acc;
- return {
- ...acc,
- [conf.category]: [
- ...(acc[conf.category] || []),
- conf,
- ]
- }
- }, {})
- return (
-
- {Object.keys(categories).map((category, idx) => (
- ))}
-
- );
-}
diff --git a/public/components/settings/configuration/components/categories/components/category/category.tsx b/public/components/settings/configuration/components/categories/components/category/category.tsx
index cee948b173..f116940cf2 100644
--- a/public/components/settings/configuration/components/categories/components/category/category.tsx
+++ b/public/components/settings/configuration/components/categories/components/category/category.tsx
@@ -12,8 +12,6 @@
*/
import React, { } from 'react';
-import { FieldForm } from './components';
-import { ISetting } from '../../../../configuration';
import {
EuiFlexItem,
EuiPanel,
@@ -22,56 +20,108 @@ import {
EuiForm,
EuiDescribedFormGroup,
EuiTitle,
- EuiFormRow
+ EuiSpacer,
+ EuiToolTip,
+ EuiButtonIcon,
} from '@elastic/eui';
import { EuiIconTip } from '@elastic/eui';
+import { TPluginSettingWithKey } from '../../../../../../../../common/constants';
+import { getPluginSettingDescription } from '../../../../../../../../common/services/settings';
+import { webDocumentationLink } from '../../../../../../../../common/services/web_documentation';
+import classNames from 'classnames';
+import { InputForm } from '../../../../../../common/form';
+
interface ICategoryProps {
- name: string
- items: ISetting[]
- updatedConfig: { [field: string]: string | number | boolean | [] }
- setUpdatedConfig({ }): void
+ title: string
+ description?: string
+ documentationLink?: string
+ items: TPluginSettingWithKey[]
+ currentConfiguration: { [field: string]: any }
+ changedConfiguration: { [field: string]: any }
+ onChangeFieldForm: () => void
}
-export const Category: React.FunctionComponent = ({ name, items, updatedConfig, setUpdatedConfig }) => {
+export const Category: React.FunctionComponent = ({
+ title,
+ description,
+ documentationLink,
+ items
+}) => {
return (
- {name}
+ {title}{
+ documentationLink &&
+
+ <>
+
+
+ >
+
+ }
+
+ {
+ description &&
+ <>
+
+
+
+ {description}
+
+
+
+
+ >
+ }
- {items.map((item, idx) => (
-
-
- {item.name}
- {isUpdated(updatedConfig, item)
- && {
+ const isUpdated = item.changed && !item.error;
+ return (
+
+
+ {item.title}
+ {isUpdated && (
+ }
- }
- description={item.description} >
-
-
-
-
- ))}
+ type='dot'
+ color='warning'
+ aria-label={item.key}
+ content='Unsaved' />
+ )}
+
+ }
+ description={getPluginSettingDescription(item)} >
+
+
+ )
+ })}
)
-}
-
-const isUpdated = (configs, item) => typeof configs[item.setting] !== 'undefined'
\ No newline at end of file
+};
diff --git a/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap b/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap
deleted file mode 100644
index 55482f7924..0000000000
--- a/public/components/settings/configuration/components/categories/components/category/components/__snapshots__/field-form.test.tsx.snap
+++ /dev/null
@@ -1,67 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FieldForm component renders correctly to match the snapshot 1`] = `
-
-
-
-
-
-
-
-
-
-`;
diff --git a/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx b/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx
deleted file mode 100644
index e551966749..0000000000
--- a/public/components/settings/configuration/components/categories/components/category/components/field-form.test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Wazuh app - React test for FieldForm component.
- *
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- *
- */
-
-import React from 'react';
-import { FieldForm } from './field-form';
-import { ISetting } from '../../../../../configuration';
-import { mount } from 'enzyme';
-
-describe('FieldForm component', () => {
- it('renders correctly to match the snapshot', () => {
- const item: ISetting = {
- setting: 'string',
- value: 'boolean',
- description: 'string',
- category: 'string',
- name: 'string',
- form: { type: 'text', params: {} }
- };
- const updatedConfig = {};
- const setUpdatedConfig = jest.fn();
-
- const wrapper = mount(
-
- );
-
- expect(wrapper).toMatchSnapshot();
- });
-});
diff --git a/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx b/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx
deleted file mode 100644
index 54abed4317..0000000000
--- a/public/components/settings/configuration/components/categories/components/category/components/field-form.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Wazuh app - React component building the configuration component.
- *
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { useState, useEffect } from 'react';
-import { validate } from 'node-cron';
-import {
- EuiFieldText,
- EuiFieldNumber,
- EuiSwitch,
- EuiSelect,
- EuiCodeEditor,
- EuiTextColor
-} from '@elastic/eui';
-import { ISetting } from '../../../../../configuration';
-import 'brace/mode/javascript';
-import 'brace/snippets/javascript';
-import 'brace/ext/language_tools';
-import "brace/ext/searchbox";
-import {
- UI_ERROR_SEVERITIES,
- UIErrorLog, UIErrorSeverity,
- UILogLevel,
-} from '../../../../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../../../../react-services/common-services';
-import _ from 'lodash';
-
-interface IFieldForm {
- item: ISetting
- updatedConfig: { [field: string]: string | number | boolean | [] }
- setUpdatedConfig({ }): void
-}
-export const FieldForm: React.FunctionComponent = (props) => {
- const { item } = props;
- switch (item.form.type) {
- case 'text':
- return
- case 'number':
- return
- case 'boolean':
- return
- case 'list':
- return
- case 'array':
- return
- case 'interval':
- return
- default:
- return null;
- }
-};
-
-//#region forms
-const TextForm: React.FunctionComponent = (props) => (
- onChange(e.target.value, props)} />);
-
-const NumberForm: React.FunctionComponent = (props) => (
- onChange(e.target.value, props)} />)
-const BooleanForm: React.FunctionComponent = (props) => (
- onChange(e.target.checked, props)} />);
-
-const ListForm: React.FunctionComponent = (props) => (
- onChange(e.target.value, props)} />);
-
-const IntervalForm: React.FunctionComponent = (props) => {
- const [interval, setInterval] = useState(getValue(props));
- const [invalid, setInvalid] = useState(false);
- useEffect(() => {
- if (validate(interval)) {
- setInvalid(false);
- getValue(props) !== interval && onChange(interval, props);
- } else {
- setInvalid(true);
- deleteChange(props);
- }
- }, [interval])
- return (
- <>
- setInterval(e.target.value)}
- />
- {invalid && Invalid cron schedule expressions}
- >
- );
-}
-
-const ArrayForm: React.FunctionComponent = (props) => {
- const [list, setList] = useState(JSON.stringify(getValue(props)));
-
- useEffect(() => {
- checkErrors();
- }, [list]);
-
- const checkErrors = () => {
- try {
- const parsed = JSON.parse(list);
- onChange(parsed, props);
- } catch (error) {
- const options: UIErrorLog = {
- context: `${FieldForm.name}.checkErrors`,
- level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
- severity: UI_ERROR_SEVERITIES.UI as UIErrorSeverity,
- error: {
- error: error,
- message: error.message || error,
- title: error.message || error,
- },
- };
-
- getErrorOrchestrator().handleError(options);
- }
- }
- return (
-
- );
-}
-
-//#endregion
-
-//#region Helpers
-
-const getValue = ({ item, updatedConfig }: IFieldForm) => typeof updatedConfig[item.setting] !== 'undefined'
- ? updatedConfig[item.setting]
- : item.value;
-
-const onChange = (value: string | number | boolean | [], props: IFieldForm) => {
- const { updatedConfig, setUpdatedConfig, item } = props;
- if(!_.isEqual(item.value,value)){
- setUpdatedConfig({
- ...updatedConfig,
- [item.setting]: value,
- })
- }else{
- deleteChange(props);
- }
-}
-
-const deleteChange = (props: IFieldForm) => {
- const { updatedConfig, setUpdatedConfig, item } = props;
- const newConfig = { ...updatedConfig };
- delete newConfig[item.setting];
- setUpdatedConfig(newConfig);
-}
-
-//#endregion
diff --git a/public/components/settings/configuration/components/categories/components/category/components/index.ts b/public/components/settings/configuration/components/categories/components/category/components/index.ts
deleted file mode 100644
index f8dd9704e0..0000000000
--- a/public/components/settings/configuration/components/categories/components/category/components/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Wazuh app - React component building the configuration component.
- *
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-
-export { FieldForm } from './field-form';
\ No newline at end of file
diff --git a/public/components/settings/configuration/components/categories/index.ts b/public/components/settings/configuration/components/categories/index.ts
deleted file mode 100644
index f665665238..0000000000
--- a/public/components/settings/configuration/components/categories/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Wazuh app - React component building the configuration component.
- *
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-export { Categories } from './categories';
\ No newline at end of file
diff --git a/public/components/settings/configuration/components/header.tsx b/public/components/settings/configuration/components/header.tsx
index baee512ec3..7bc47fb1d3 100644
--- a/public/components/settings/configuration/components/header.tsx
+++ b/public/components/settings/configuration/components/header.tsx
@@ -11,8 +11,7 @@
* Find more information about this on the LICENSE file.
*/
-import React, { useState, useEffect, Fragment } from 'react';
-import {categoriesNames} from '../../../../../common/config-equivalences';
+import React, { useState, useEffect } from 'react';
import { AppNavigate } from '../../../../react-services/app-navigate';
import {
EuiFlexGroup,
@@ -28,7 +27,7 @@ import { PLUGIN_PLATFORM_WAZUH_DOCUMENTATION_URL_PATH_APP_CONFIGURATION } from '
import { getPluginDataPath } from '../../../../../common/plugin';
import { webDocumentationLink } from '../../../../../common/services/web_documentation';
-export const Header = ({query, setQuery}) => {
+export const Header = ({query, setQuery, searchBarFilters}) => {
return (
@@ -39,13 +38,12 @@ export const Header = ({query, setQuery}) => {
-
+
)
-}
-
+};
const Title = () => {
return (
@@ -68,7 +66,7 @@ const Title = () => {
)
-}
+};
const SubTitle = () => {
return (
@@ -78,16 +76,15 @@ const SubTitle = () => {
)
-}
+};
-const SearchBar = ({query, setQuery}) => {
- const [categories, setCategories] = useState([]);
+const SearchBar = ({query, setQuery, searchBarFilters}) => {
const [error, setError] = useState();
+
useEffect(() => {
- const cats = categoriesNames.map(item => ({value: item}));
- setCategories(cats);
getDefaultCategory(setQuery)
- }, [])
+ }, []);
+
const onChange = (args) => {
if(args.error){
setError(args.error);
@@ -95,28 +92,23 @@ const SearchBar = ({query, setQuery}) => {
setError(undefined);
setQuery(args);
}
- }
+ };
+
return (
-
+ <>
{!!error &&
{`${error.name}: ${error.message}`}
}
-
+ >
)
-}
+};
const getDefaultCategory = (setQuery) => {
const category:string | undefined = AppNavigate.getUrlParameter('category')
category && setQuery(`category:(${category})`)
-}
+};
diff --git a/public/components/settings/configuration/components/index.ts b/public/components/settings/configuration/components/index.ts
index 4b36cdf37d..cbc25aaeb7 100644
--- a/public/components/settings/configuration/components/index.ts
+++ b/public/components/settings/configuration/components/index.ts
@@ -11,5 +11,4 @@
* Find more information about this on the LICENSE file.
*/
export { Header } from './header';
-export { Categories } from './categories';
export { BottomBar } from './bottom-bar';
\ No newline at end of file
diff --git a/public/components/settings/configuration/configuration.tsx b/public/components/settings/configuration/configuration.tsx
index 780af7476f..b8ccba3f2b 100644
--- a/public/components/settings/configuration/configuration.tsx
+++ b/public/components/settings/configuration/configuration.tsx
@@ -12,71 +12,211 @@
*/
import React, { useState, useEffect } from 'react';
-import { Header, Categories, BottomBar } from './components';
+import { useDispatch, useSelector } from 'react-redux';
+import { Header, BottomBar } from './components';
import { useKbnLoadingIndicator} from '../../common/hooks';
+import { useForm } from '../../common/form/hooks';
import {
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
EuiPage,
EuiPageBody,
EuiPageHeader,
EuiSpacer,
Query,
} from '@elastic/eui';
-import {
- configEquivalences,
- nameEquivalence,
- categoriesEquivalence,
- formEquivalence
-} from '../../../../common/config-equivalences';
import store from '../../../redux/store'
import { updateSelectedSettingsSection } from '../../../redux/actions/appStateActions';
-import { withUserAuthorizationPrompt, withErrorBoundary, withReduxProvider } from '../../common/hocs'
-import { WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants';
+import { withUserAuthorizationPrompt, withErrorBoundary, withReduxProvider } from '../../common/hocs';
+import { PLUGIN_PLATFORM_NAME, PLUGIN_SETTINGS, PLUGIN_SETTINGS_CATEGORIES, UI_LOGGER_LEVELS, WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../../common/constants';
import { compose } from 'redux';
+import { getSettingsDefaultList, groupSettingsByCategory, getCategorySettingByTitle } from '../../../../common/services/settings';
+import { Category } from './components/categories/components';
+import { WzRequest } from '../../../react-services';
+import { UIErrorLog, UIErrorSeverity, UILogLevel, UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types';
+import { getErrorOrchestrator } from '../../../react-services/common-services';
+import { getToasts } from '../../../kibana-services';
+import { updateAppConfig } from '../../../redux/actions/appConfigActions';
export type ISetting = {
- setting: string
+ key: string
value: boolean | string | number | object
description: string
category: string
name: string
readonly?: boolean
form: { type: string, params: {} }
-}
+};
+
+const pluginSettingConfigurableUI = getSettingsDefaultList()
+ .filter(categorySetting => categorySetting.isConfigurableFromUI)
+ .map(setting => ({ ...setting, category: PLUGIN_SETTINGS_CATEGORIES[setting.category].title}));
+
+const settingsCategoriesSearchBarFilters = [...new Set(pluginSettingConfigurableUI.map(({category}) => category))].sort().map(category => ({value: category}))
+
+const trasnsfromPluginSettingsToFormFields = configuration => Object.fromEntries(
+ getSettingsDefaultList()
+ .filter(pluginSetting => pluginSetting.isConfigurableFromUI)
+ .map(({
+ key,
+ type,
+ validate,
+ defaultValue: initialValue,
+ uiFormTransformChangedInputValue,
+ uiFormTransformConfigurationValueToInputValue,
+ uiFormTransformInputValueToConfigurationValue,
+ ...rest
+ }) => ([
+ key,
+ {
+ type,
+ validate: validate?.bind?.(rest),
+ transformChangedInputValue: uiFormTransformChangedInputValue?.bind?.(rest),
+ transformChangedOutputValue: uiFormTransformInputValueToConfigurationValue?.bind?.(rest),
+ initialValue: uiFormTransformConfigurationValueToInputValue ? uiFormTransformConfigurationValueToInputValue.bind(rest)(configuration?.[key] ?? initialValue) : (configuration?.[key] ?? initialValue)
+ }
+ ]))
+);
const WzConfigurationSettingsProvider = (props) => {
const [loading, setLoading ] = useKbnLoadingIndicator();
- const [config, setConfig] = useState([]);
const [query, setQuery] = useState('');
- const [updatedConfig, setUpdateConfig] = useState({});
+ const currentConfiguration = useSelector(state => state.appConfig.data);
+
+ const { fields, changed, errors, doChanges, undoChanges } = useForm(trasnsfromPluginSettingsToFormFields(currentConfiguration));
+ const dispatch = useDispatch();
+
useEffect(() => {
store.dispatch(updateSelectedSettingsSection('configuration'));
- const rawConfig = props.wazuhConfig.getConfig();
- const formatedConfig = Object.keys(rawConfig).reduce((acc, conf) => [
- ...acc,
- {
- setting: conf,
- value: rawConfig[conf],
- description: configEquivalences[conf],
- category: categoriesEquivalence[conf],
- name: nameEquivalence[conf],
- form: formEquivalence[conf],
- }
- ], []);
- setConfig(formatedConfig);
}, []);
+
+ const onChangeSearchQuery = (query) => {
+ setQuery(query);
+ };
+
+ const visibleSettings = Object.entries(fields)
+ .map(([fieldKey, fieldForm]) => ({
+ ...fieldForm,
+ key: fieldKey,
+ category: PLUGIN_SETTINGS_CATEGORIES[PLUGIN_SETTINGS[fieldKey].category].title,
+ type: PLUGIN_SETTINGS[fieldKey].type,
+ options: PLUGIN_SETTINGS[fieldKey]?.options,
+ title: PLUGIN_SETTINGS[fieldKey]?.title,
+ description: PLUGIN_SETTINGS[fieldKey]?.description
+ }));
+
+ // https://github.com/elastic/eui/blob/aa4cfd7b7c34c2d724405a3ecffde7fe6cf3b50f/src/components/search_bar/query/query.ts#L138-L163
+ const search = Query.execute(query.query || query, visibleSettings, ['description', 'key', 'title']);
+
+ const visibleCategories = groupSettingsByCategory(search || visibleSettings)
+ // Sort categories to render them in their enum definition order
+ .sort((a, b) =>
+ (getCategorySettingByTitle(a.category)?.renderOrder || 0) -
+ (getCategorySettingByTitle(b.category)?.renderOrder || 0)
+ );
+
+ const onSave = async () => {
+ setLoading(true);
+ try {
+ const settingsToUpdate = Object.entries(changed).reduce((accum, [pluginSettingKey, currentValue]) => {
+ if(PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile){
+ accum.saveOnConfigurationFile = {
+ ...accum.saveOnConfigurationFile,
+ [pluginSettingKey]: currentValue
+ }
+ };
+ return accum;
+ }, {saveOnConfigurationFile: {}});
+
+ const requests = [];
+
+ if(Object.keys(settingsToUpdate.saveOnConfigurationFile).length){
+ requests.push(WzRequest.genericReq(
+ 'PUT', '/utils/configuration',
+ settingsToUpdate.saveOnConfigurationFile
+ ));
+ };
+ const responses = await Promise.all(requests);
+
+ // Show the toasts if necessary
+ responses.some(({data: { data: {requiresRunningHealthCheck}}}) => requiresRunningHealthCheck) && toastRequiresRunningHealthcheck();
+ responses.some(({data: { data: {requiresReloadingBrowserTab}}}) => requiresReloadingBrowserTab) && toastRequiresReloadingBrowserTab();
+ responses.some(({data: { data: {requiresRestartingPluginPlatform}}}) => requiresRestartingPluginPlatform) && toastRequiresRestartingPluginPlatform();
+
+ // Update the app configuration frontend-cached setting in memory with the new values
+ dispatch(updateAppConfig({
+ ...responses.reduce((accum, {data: {data}}) => {
+ return {
+ ...accum,
+ ...(data.updatedConfiguration ? {...data.updatedConfiguration} : {}),
+ }
+ },{})
+ }));
+
+ // Show the success toast
+ successToast();
+
+ // Reset the form changed configuration
+ doChanges();
+ } catch (error) {
+ const options: UIErrorLog = {
+ context: `${WzConfigurationSettingsProvider.name}.onSave`,
+ level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
+ severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity,
+ store: true,
+ error: {
+ error: error,
+ message: error.message || error,
+ title: `Error saving the configuration: ${error.message || error}`,
+ },
+ };
+
+ getErrorOrchestrator().handleError(options);
+ } finally {
+ setLoading(false);
+ };
+ };
+
return (
-
+
-
+
+ {visibleCategories && visibleCategories.map(({ category, settings }) => {
+ const { description, documentationLink } = getCategorySettingByTitle(category);
+ return (
+
+ )
+ }
+ )}
+
-
+ {Object.keys(changed).length > 0 && (
+
+ )}
);
@@ -86,3 +226,46 @@ export const WzConfigurationSettings = compose (
withReduxProvider,
withUserAuthorizationPrompt(null, [WAZUH_ROLE_ADMINISTRATOR_NAME])
)(WzConfigurationSettingsProvider);
+
+const toastRequiresReloadingBrowserTab = () => {
+ getToasts().add({
+ color: 'success',
+ title: 'This setting requires you to reload the page to take effect.',
+ text:
+
+ window.location.reload()} size="s">Reload page
+
+
+ });
+};
+
+const toastRequiresRunningHealthcheck = () => {
+ const toast = getToasts().add({
+ color: 'warning',
+ title: 'You must execute the health check for the changes to take effect',
+ toastLifeTimeMs: 5000,
+ text:
+
+
+ {
+ getToasts().remove(toast);
+ window.location.href = '#/health-check';
+ }} size="s">Execute health check
+
+
+ });
+};
+
+const toastRequiresRestartingPluginPlatform = () => {
+ getToasts().add({
+ color: 'warning',
+ title: `You must restart ${PLUGIN_PLATFORM_NAME} for the changes to take effect`,
+ });
+};
+
+const successToast = () => {
+ getToasts().add({
+ color: 'success',
+ title: 'The configuration has been successfully updated',
+ });
+};
diff --git a/public/components/settings/configuration/utils/configuration-handler.js b/public/components/settings/configuration/utils/configuration-handler.js
deleted file mode 100644
index 8c15295277..0000000000
--- a/public/components/settings/configuration/utils/configuration-handler.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Wazuh app - Configuration handler service
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-
-import { WzRequest } from '../../../../react-services/wz-request';
-import { AppState } from '../../../../react-services/app-state';
-
-export default class ConfigurationHandler {
- /**
- * Set the configuration key
- */
- static async editKey(key, value) {
- try {
- if (key === 'ip.selector') {
- AppState.setPatternSelector(value);
- }
- const result = await WzRequest.genericReq('PUT', '/utils/configuration', {
- key,
- value
- });
- return result;
- } catch (error) {
- return Promise.reject(error);
- }
- }
-}
diff --git a/public/components/wz-agent-selector/wz-agent-selector.js b/public/components/wz-agent-selector/wz-agent-selector.js
index 00fbdc3969..bb8fc7357c 100644
--- a/public/components/wz-agent-selector/wz-agent-selector.js
+++ b/public/components/wz-agent-selector/wz-agent-selector.js
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
import { showExploreAgentModalGlobal } from '../../redux/actions/appStateActions';
import store from '../../redux/store';
import { AgentSelectionTable } from '../../controllers/overview/components/overview-actions/agents-selection-table';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
import { AppState } from '../../react-services/app-state';
import { getAngularModule, getDataPlugin } from '../../kibana-services';
@@ -70,7 +70,7 @@ class WzAgentSelector extends Component {
"negate": false,
"params": { "query": agentIdList[0] },
"type": "phrase",
- "index": AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN
+ "index": AppState.getCurrentPattern() || getSettingDefaultValue('pattern')
},
"query": {
"match_phrase": {
diff --git a/public/controllers/agent/agents.js b/public/controllers/agent/agents.js
index b1e4ba9bf5..768655905b 100644
--- a/public/controllers/agent/agents.js
+++ b/public/controllers/agent/agents.js
@@ -28,12 +28,13 @@ import { ErrorHandler } from '../../react-services/error-handler';
import { GroupHandler } from '../../react-services/group-handler';
import store from '../../redux/store';
import { updateGlobalBreadcrumb } from '../../redux/actions/globalBreadcrumbActions';
-import { API_NAME_AGENT_STATUS, WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { API_NAME_AGENT_STATUS } from '../../../common/constants';
import { getDataPlugin } from '../../kibana-services';
import { hasAgentSupportModule } from '../../react-services/wz-agents';
import { UI_LOGGER_LEVELS } from '../../../common/constants';
import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types';
import { getErrorOrchestrator } from '../../react-services/common-services';
+import { getSettingDefaultValue } from '../../../common/services/settings';
export class AgentsController {
/**
@@ -378,10 +379,10 @@ export class AgentsController {
this.$scope.cancelAddGroup = () => (this.$scope.addingGroupToAgent = false);
this.$scope.loadScaChecks = (policy) =>
- (this.$scope.lookingSca = {
- ...policy,
- id: policy.policy_id,
- });
+ (this.$scope.lookingSca = {
+ ...policy,
+ id: policy.policy_id,
+ });
this.$scope.closeScaChecks = () => (this.$scope.lookingSca = false);
@@ -512,7 +513,7 @@ export class AgentsController {
this.$scope.agent.status;
this.$scope.isSynchronized = this.$scope.agent.status?.synced;
- this.$scope.$applyAsync();
+ this.$scope.$applyAsync();
} catch (error) {
throw new Error(error);
}
diff --git a/public/controllers/overview/components/alerts-stats.js b/public/controllers/overview/components/alerts-stats.js
index 15ab7a9463..dca476c482 100644
--- a/public/controllers/overview/components/alerts-stats.js
+++ b/public/controllers/overview/components/alerts-stats.js
@@ -17,9 +17,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';
import { connect } from 'react-redux';
import { buildPhrasesFilter, buildRangeFilter } from '../../../../../../src/plugins/data/common';
import { getIndexPattern } from '../../../../public/components/overview/mitre/lib';
-import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants';
import { AppState } from '../../../react-services/app-state';
import { getDataPlugin } from '../../../kibana-services';
+import { getSettingDefaultValue } from '../../../../common/services/settings';
class AlertsStats extends Component {
@@ -90,7 +90,7 @@ class AlertsStats extends Component {
"params": { "query": filter.value },
"type": "phrase",
"negate": filter.negate || false,
- "index": AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN
+ "index": AppState.getCurrentPattern() || getSettingDefaultValue('pattern')
},
"query": { "match_phrase": matchPhrase },
"$state": { "store": "appState" }
diff --git a/public/controllers/overview/components/overview-actions/overview-actions.js b/public/controllers/overview/components/overview-actions/overview-actions.js
index 877dab28d7..36885d4f37 100644
--- a/public/controllers/overview/components/overview-actions/overview-actions.js
+++ b/public/controllers/overview/components/overview-actions/overview-actions.js
@@ -27,9 +27,9 @@ import {
import { WzButton } from '../../../../components/common/buttons';
import './agents-selector.scss';
import { AgentSelectionTable } from './agents-selection-table';
-import { WAZUH_ALERTS_PATTERN } from '../../../../../common/constants';
import { AppState } from '../../../../react-services/app-state';
import { getDataPlugin } from '../../../../kibana-services';
+import { getSettingDefaultValue } from '../../../../../common/services/settings';
class OverviewActions extends Component {
constructor(props) {
@@ -110,7 +110,7 @@ class OverviewActions extends Component {
negate: false,
params: { query: agentIdList[0] },
type: 'phrase',
- index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN,
+ index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'),
},
query: {
match_phrase: {
diff --git a/public/controllers/overview/overview.js b/public/controllers/overview/overview.js
index d92f501ff1..f74c342f2e 100644
--- a/public/controllers/overview/overview.js
+++ b/public/controllers/overview/overview.js
@@ -22,10 +22,11 @@ import { updateCurrentTab, updateCurrentAgentData } from '../../redux/actions/ap
import { VisFactoryHandler } from '../../react-services/vis-factory-handler';
import { RawVisualizations } from '../../factories/raw-visualizations';
import store from '../../redux/store';
-import { UI_LOGGER_LEVELS, WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { UI_LOGGER_LEVELS } from '../../../common/constants';
import { getDataPlugin } from '../../kibana-services';
import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types';
import { getErrorOrchestrator } from '../../react-services/common-services';
+import { getSettingDefaultValue } from '../../../common/services/settings';
export class OverviewController {
/**
@@ -168,7 +169,7 @@ export class OverviewController {
}
async updateSelectedAgents(agentList) {
- try{
+ try {
if (this.initialFilter) {
this.initialFilter = false;
this.agentsSelectionProps.initialFilter = false;
@@ -176,14 +177,14 @@ export class OverviewController {
this.isAgent = agentList && agentList.length ? agentList[0] : false;
this.$scope.isAgentText = this.isAgent && agentList.length === 1 ? ` of agent ${agentList.toString()}` : this.isAgent && agentList.length > 1 ? ` of ${agentList.length.toString()} agents` : false;
if (agentList && agentList.length) {
- await this.visFactoryService.buildAgentsVisualizations(
- this.filterHandler,
- this.tab,
- this.tabView,
- agentList[0],
- (this.tabView === 'discover' || this.oldFilteredTab === this.tab)
- );
- this.oldFilteredTab = this.tab;
+ await this.visFactoryService.buildAgentsVisualizations(
+ this.filterHandler,
+ this.tab,
+ this.tabView,
+ agentList[0],
+ (this.tabView === 'discover' || this.oldFilteredTab === this.tab)
+ );
+ this.oldFilteredTab = this.tab;
} else if (!agentList && this.tab !== 'welcome') {
if (!store.getState().appStateReducers.currentAgentData.id) {
await this.visFactoryService.buildOverviewVisualizations(
@@ -196,10 +197,10 @@ export class OverviewController {
}
};
setTimeout(() => { this.$location.search('agentId', store.getState().appStateReducers.currentAgentData.id ? String(store.getState().appStateReducers.currentAgentData.id) : null) }, 1);
-
+
this.visualizeProps["isAgent"] = agentList ? agentList[0] : false;
this.$rootScope.$applyAsync();
- }catch(error){
+ } catch (error) {
const options = {
context: `${OverviewController.name}.updateSelectedAgents`,
level: UI_LOGGER_LEVELS.ERROR,
@@ -329,7 +330,7 @@ export class OverviewController {
*/
async getSummary() {
try {
- const {data: { data: {connection: data} }} = await WzRequest.apiReq('GET', '/agents/summary/status', {});
+ const { data: { data: { connection: data } } } = await WzRequest.apiReq('GET', '/agents/summary/status', {});
this.agentsCount = data;
this.welcomeCardsProps.agentsCountTotal = data.total;
this.agentsCoverity = data.total ? (data.active / data.total) * 100 : 0;
diff --git a/public/kibana-integrations/kibana-discover.js b/public/kibana-integrations/kibana-discover.js
index 2e87ba7449..ce5eadfec0 100644
--- a/public/kibana-integrations/kibana-discover.js
+++ b/public/kibana-integrations/kibana-discover.js
@@ -83,7 +83,6 @@ import {
UI_SETTINGS,
} from '../../../../src/plugins/data/public';
import { addFatalError } from '../../../../src/plugins/kibana_legacy/public';
-import { WAZUH_ALERTS_PATTERN } from '../../common/constants';
import {
DEFAULT_COLUMNS_SETTING,
SAMPLE_SIZE_SETTING,
@@ -96,6 +95,7 @@ import { createFixedScroll } from './discover/application/angular/directives/fix
import './discover/application/index.scss';
import { getFilterWithAuthorizedAgents } from '../react-services/filter-authorization-agents';
+import { getSettingDefaultValue } from '../../common/services/settings';
const fetchStatuses = {
UNINITIALIZED: 'uninitialized',
@@ -552,7 +552,7 @@ function discoverController(
negate: true,
params: { query: '000' },
type: 'phrase',
- index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN
+ index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern')
},
query: { match_phrase: { 'agent.id': '000' } },
$state: { store: 'appState' }
diff --git a/public/redux/reducers/appConfigReducers.ts b/public/redux/reducers/appConfigReducers.ts
index 4157abf4ef..5f3d43fd92 100644
--- a/public/redux/reducers/appConfigReducers.ts
+++ b/public/redux/reducers/appConfigReducers.ts
@@ -11,14 +11,14 @@
*/
import { Reducer } from 'redux';
-import { WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants';
+import { getSettingsDefault } from '../../../common/services/settings';
import { AppConfigState, ResolverAction } from '../types';
const initialState: AppConfigState = {
isLoading: false,
isReady: false,
hasError: false,
- data: WAZUH_DEFAULT_APP_CONFIG,
+ data: getSettingsDefault(),
};
const appConfigReducer: Reducer = (
diff --git a/public/services/resolves/get-config.js b/public/services/resolves/get-config.js
index ba4aa543c1..63f055e951 100644
--- a/public/services/resolves/get-config.js
+++ b/public/services/resolves/get-config.js
@@ -10,10 +10,10 @@
* Find more information about this on the LICENSE file.
*/
-import { WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants';
+import { getSettingsDefault } from '../../../common/services/settings';
export async function getWzConfig($q, genericReq, wazuhConfig) {
- const defaultConfig = { ...WAZUH_DEFAULT_APP_CONFIG };
+ const defaultConfig = getSettingsDefault();
try {
const config = await genericReq.request('GET', '/utils/configuration', {});
diff --git a/server/controllers/wazuh-elastic.ts b/server/controllers/wazuh-elastic.ts
index c0a2788eba..6054aca118 100644
--- a/server/controllers/wazuh-elastic.ts
+++ b/server/controllers/wazuh-elastic.ts
@@ -19,12 +19,13 @@ import {
} from '../integration-files/visualizations';
import { generateAlerts } from '../lib/generate-alerts/generate-alerts-script';
-import { WAZUH_MONITORING_PATTERN, WAZUH_SAMPLE_ALERT_PREFIX, WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_SAMPLE_ALERTS_INDEX_SHARDS, WAZUH_SAMPLE_ALERTS_INDEX_REPLICAS } from '../../common/constants';
+import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_SAMPLE_ALERTS_INDEX_SHARDS, WAZUH_SAMPLE_ALERTS_INDEX_REPLICAS } from '../../common/constants';
import jwtDecode from 'jwt-decode';
import { ManageHosts } from '../lib/manage-hosts';
import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory, SavedObject, SavedObjectsFindResponse } from 'src/core/server';
import { getCookieValueByName } from '../lib/cookie';
import { WAZUH_SAMPLE_ALERTS_CATEGORIES_TYPE_ALERTS, WAZUH_SAMPLE_ALERTS_DEFAULT_NUMBER_ALERTS } from '../../common/constants'
+import { getSettingDefaultValue } from '../../common/services/settings';
export class WazuhElasticCtrl {
wzSampleAlertsIndexPrefix: string
@@ -47,7 +48,7 @@ export class WazuhElasticCtrl {
*/
getSampleAlertPrefix(): string {
const config = getConfiguration();
- return config['alerts.sample.prefix'] || WAZUH_SAMPLE_ALERT_PREFIX;
+ return config['alerts.sample.prefix'] || getSettingDefaultValue('alerts.sample.prefix');
}
/**
@@ -345,7 +346,7 @@ export class WazuhElasticCtrl {
try {
const config = getConfiguration();
let monitoringPattern =
- (config || {})['wazuh.monitoring.pattern'] || WAZUH_MONITORING_PATTERN;
+ (config || {})['wazuh.monitoring.pattern'] || getSettingDefaultValue('wazuh.monitoring.pattern');
log(
'wazuh-elastic:buildVisualizationsRaw',
`Building ${app_objects.length} visualizations`,
diff --git a/server/controllers/wazuh-utils/wazuh-utils.ts b/server/controllers/wazuh-utils/wazuh-utils.ts
index 9a33c3c649..ecf380f4b2 100644
--- a/server/controllers/wazuh-utils/wazuh-utils.ts
+++ b/server/controllers/wazuh-utils/wazuh-utils.ts
@@ -16,7 +16,7 @@ import { getConfiguration } from '../../lib/get-configuration';
import { read } from 'read-last-lines';
import { UpdateConfigurationFile } from '../../lib/update-configuration';
import jwtDecode from 'jwt-decode';
-import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, WAZUH_UI_LOGS_RAW_PATH } from '../../../common/constants';
+import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, PLUGIN_SETTINGS } from '../../../common/constants';
import { ManageHosts } from '../../lib/manage-hosts';
import { KibanaRequest, RequestHandlerContext, KibanaResponseFactory } from 'src/core/server';
import { getCookieValueByName } from '../../lib/cookie';
@@ -62,41 +62,35 @@ export class WazuhUtilsCtrl {
* @param {Object} response
* @returns {Object} Configuration File or ErrorResponse
*/
- async updateConfigurationFile(context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) {
- try {
- // Check if user has administrator role in token
- const token = getCookieValueByName(request.headers.cookie,'wz-token');
- if(!token){
- return ErrorResponse('No token provided', 401, 401, response);
- };
- const decodedToken = jwtDecode(token);
- if(!decodedToken){
- return ErrorResponse('No permissions in token', 401, 401, response);
- };
- if(!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)){
- return ErrorResponse('No administrator role', 401, 401, response);
- };response
- // Check the provided token is valid
- const apiHostID = getCookieValueByName(request.headers.cookie,'wz-api');
- if( !apiHostID ){
- return ErrorResponse('No API id provided', 401, 401, response);
- };
- const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, {apiHostID});
- if(responseTokenIsWorking.status !== 200){
- return ErrorResponse('Token is not valid', 401, 401, response);
+ updateConfigurationFile = this.routeDecoratorProtectedAdministratorRoleValidToken(
+ async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => {
+
+ let requiresRunningHealthCheck: boolean = false,
+ requiresReloadingBrowserTab: boolean = false,
+ requiresRestartingPluginPlatform: boolean = false;
+
+ // Plugin settings configurables in the configuration file.
+ const pluginSettingsConfigurableFile = Object.keys(request.body)
+ .filter(pluginSettingKey => PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile)
+ .reduce((accum, pluginSettingKey: string) => ({...accum, [pluginSettingKey]: request.body[pluginSettingKey]}), {});
+
+ if(Object.keys(pluginSettingsConfigurableFile).length){
+ // Update the configuration file.
+ await updateConfigurationFile.updateConfiguration(pluginSettingsConfigurableFile);
+
+ requiresRunningHealthCheck = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRunningHealthCheck)) || requiresRunningHealthCheck;
+ requiresReloadingBrowserTab = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresReloadingBrowserTab)) || requiresReloadingBrowserTab;
+ requiresRestartingPluginPlatform = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRestartingPluginPlatform)) || requiresRestartingPluginPlatform;
};
- const result = await updateConfigurationFile.updateConfiguration(request);
+
return response.ok({
body: {
- statusCode: 200,
- error: 0,
- data: result
+ data: { requiresRunningHealthCheck, requiresReloadingBrowserTab, requiresRestartingPluginPlatform, updatedConfiguration: request.body }
}
});
- } catch (error) {
- return ErrorResponse(error.message || error, 3021, 500, response);
- }
- }
+ },
+ 3021
+ )
/**
* Returns Wazuh app logs
@@ -127,5 +121,34 @@ export class WazuhUtilsCtrl {
}
}
-
+ private routeDecoratorProtectedAdministratorRoleValidToken(routeHandler, errorCode: number){
+ return async (context, request, response) => {
+ try{
+ // Check if user has administrator role in token
+ const token = getCookieValueByName(request.headers.cookie,'wz-token');
+ if(!token){
+ return ErrorResponse('No token provided', 401, 401, response);
+ };
+ const decodedToken = jwtDecode(token);
+ if(!decodedToken){
+ return ErrorResponse('No permissions in token', 401, 401, response);
+ };
+ if(!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)){
+ return ErrorResponse('No administrator role', 401, 401, response);
+ };
+ // Check the provided token is valid
+ const apiHostID = getCookieValueByName(request.headers.cookie,'wz-api');
+ if( !apiHostID ){
+ return ErrorResponse('No API id provided', 401, 401, response);
+ };
+ const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, {apiHostID});
+ if(responseTokenIsWorking.status !== 200){
+ return ErrorResponse('Token is not valid', 401, 401, response);
+ };
+ return await routeHandler(context, request, response)
+ }catch(error){
+ return ErrorResponse(error.message || error, errorCode, 500, response);
+ }
+ }
+ }
}
diff --git a/server/lib/get-configuration.ts b/server/lib/get-configuration.ts
index 8e6d2d5d2a..01edd1cf6d 100644
--- a/server/lib/get-configuration.ts
+++ b/server/lib/get-configuration.ts
@@ -11,29 +11,62 @@
*/
import fs from 'fs';
import yml from 'js-yaml';
-import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_CACHE_TIME } from '../../common/constants';
+import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_CACHE_TIME, PLUGIN_SETTINGS, EpluginSettingType } from '../../common/constants';
let cachedConfiguration: any = null;
let lastAssign: number = new Date().getTime();
-export function getConfiguration(isUpdating: boolean = false) {
+/**
+ * Get the plugin configuration and cache it.
+ * @param options.force Force to read the configuration and no use the cache .
+ * @returns plugin configuration in JSON
+ */
+export function getConfiguration(options: {force?: boolean} = {}) {
try {
const now = new Date().getTime();
const dateDiffer = now - lastAssign;
- if (!cachedConfiguration || dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || isUpdating) {
- const raw = fs.readFileSync(WAZUH_DATA_CONFIG_APP_PATH, { encoding: 'utf-8' });
- const file = yml.load(raw);
+ if (!cachedConfiguration || dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || options?.force) {
+ cachedConfiguration = obfuscateHostsConfiguration(
+ readPluginConfigurationFile(WAZUH_DATA_CONFIG_APP_PATH),
+ ['password']
+ );
- for (const host of file.hosts) {
- Object.keys(host).forEach((k) => {
- host[k].password = '*****';
- });
- }
- cachedConfiguration = { ...file };
lastAssign = now;
}
return cachedConfiguration;
} catch (error) {
return false;
- }
-}
+ };
+};
+
+/**
+ * Read the configuration file and transform to JSON.
+ * @param path File path of the plugin configuration file.
+ * @returns Configuration as JSON.
+ */
+ function readPluginConfigurationFile(filepath: string) {
+ const content = fs.readFileSync(filepath, { encoding: 'utf-8' });
+ return yml.load(content);
+};
+
+/**
+ * Obfuscate fields of the hosts configuration.
+ * @param configuration Plugin configuration as JSON.
+ * @param obfuscateHostConfigurationKeys Keys to obfuscate its value in the hosts configuration.
+ * @returns
+ */
+function obfuscateHostsConfiguration(configuration: any, obfuscateHostConfigurationKeys: string[]){
+ if(configuration.hosts){
+ configuration.hosts = Object.entries(configuration.hosts)
+ .reduce((accum, [hostID, hostConfiguration]) => {
+ return {...accum, [hostID]: {
+ ...hostConfiguration,
+ ...(obfuscateHostConfigurationKeys
+ .reduce((accumObfuscateHostConfigurationKeys, obfuscateHostConfigurationKey) =>
+ ({...accumObfuscateHostConfigurationKeys, [obfuscateHostConfigurationKey]: '*****'}), {})
+ )
+ }}
+ }, {});
+ };
+ return configuration;
+};
diff --git a/server/lib/initial-wazuh-config.test.ts b/server/lib/initial-wazuh-config.test.ts
new file mode 100644
index 0000000000..032184387f
--- /dev/null
+++ b/server/lib/initial-wazuh-config.test.ts
@@ -0,0 +1,104 @@
+import { PLUGIN_SETTINGS_CATEGORIES } from '../../common/constants';
+import {
+ header,
+ hostsConfiguration,
+ initialWazuhConfig,
+ printSetting,
+ printSettingCategory,
+ printSettingValue,
+ printSection,
+} from './initial-wazuh-config';
+import { getSettingsDefaultList, groupSettingsByCategory } from '../../common/services/settings';
+
+describe('[configuration-file] Default configuration file content', () => {
+
+ it('Include the header', () => {
+ expect(initialWazuhConfig).toContain(header);
+ });
+
+ it('Include all the expected categories and settings', () => {
+
+ const pluginSettingsConfigurationFile = getSettingsDefaultList()
+ .filter(categorySetting => categorySetting.isConfigurableFromFile);
+
+ const pluginSettingsConfigurationFileGroupByCategory = groupSettingsByCategory(pluginSettingsConfigurationFile);
+
+ pluginSettingsConfigurationFileGroupByCategory.forEach(({category, settings}) => {
+ // Category
+ expect(initialWazuhConfig).toContain(printSettingCategory(PLUGIN_SETTINGS_CATEGORIES[category]));
+
+ // Category settings
+ settings.forEach(setting => {
+ expect(initialWazuhConfig).toContain(printSetting(setting));
+ });
+ });
+
+ });
+
+ it('Include the host configuration', () => {
+ expect(initialWazuhConfig).toContain(hostsConfiguration);
+ });
+});
+
+describe('[configuration-file] Methods', () => {
+
+ it.each`
+ text | options
+ ${'Test'} | ${{}}
+ ${'Test'} | ${{ maxLength: 60, prefix: '# '}}
+ ${'Test'} | ${{ maxLength: 60, fill: '-', prefix: '# '}}
+ `('printSection: $options', ({text, options}) => {
+ const result = printSection(text, options);
+ expect(result).toHaveLength(options?.maxLength ?? 80);
+ options.prefix && expect(result).toMatch(new RegExp(`^${options.prefix}`));
+ expect(result).toMatch(new RegExp(`${options?.fill ?? ' '}$`));
+ expect(result).toContain(`${' '.repeat(options.spaceAround | 1)}${text}${' '.repeat(options.spaceAround | 1)}`);
+ });
+
+ it.each`
+ input | expected
+ ${{title: 'Test', description: 'Test description'}} | ${'# ------------------------------------ Test ------------------------------------\n#\n# Test description'}
+ ${{title: 'Test 2', description: 'Test description'}} | ${'# ----------------------------------- Test 2 -----------------------------------\n#\n# Test description'}
+ ${{title: 'Test 2', description: 'Test description'}} | ${'# ----------------------------------- Test 2 -----------------------------------\n#\n# Test description'}
+ `('printSettingValue: input: $input , expected: $expected', ({input, expected}) => {
+ const result = printSettingCategory(input);
+ expect(result).toBe(expected);
+ });
+
+ it.each(
+ [
+ {
+ input: {key: 'test', description: 'Test description', defaultValue: 0},
+ expected: '# Test description\n# test: 0'
+ },
+ {
+ input: {key: 'test', description: 'Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. Test description. ', defaultValue: 0},
+ expected: '# Test description. Test description. Test description. Test description. Test\n# description. Test description. Test description. Test description. Test\n# description. Test description. Test description.\n# test: 0'
+ },
+ {
+ input: {key: 'test', description: 'Test description', defaultValue: 0, options: {select: [{text: 'Option1', value: 'option'},{text: 'Option2', value: 'option2'}]}},
+ expected: '# Test description Allowed values: option (Option1), option2 (Option2).\n# test: 0'
+ },
+ {
+ input: {key: 'test', description: 'Test description', defaultValue: 0, options: {switch: {values: { disabled: {label: 'Enabled', value: 'disabled'}, enabled: {label: 'Enabled', value: 'enabled'}, }}}},
+ expected: '# Test description Allowed values: enabled (Enabled), disabled (Enabled).\n# test: 0'
+ }
+
+ ]
+ )('printSetting: input: $input , expected: $expected', ({input, expected}) => {
+ const result = printSetting(input);
+ expect(result).toMatch(expected);
+ });
+
+ it.each`
+ input | expected
+ ${4} | ${4}
+ ${''} | ${"''"}
+ ${'test'} | ${'test'}
+ ${{key: 'value'}} | ${'{\"key\":\"value\"}'}
+ ${[]} | ${"[]"}
+ ${''} | ${"''"}
+ `('printSettingValue: input: $input , expected: $expected', ({input, expected}) => {
+ expect(printSettingValue(input)).toBe(expected);
+ });
+});
diff --git a/server/lib/initial-wazuh-config.ts b/server/lib/initial-wazuh-config.ts
index 9e99873a11..8203a21238 100644
--- a/server/lib/initial-wazuh-config.ts
+++ b/server/lib/initial-wazuh-config.ts
@@ -11,45 +11,13 @@
*/
import {
- WAZUH_ALERTS_PATTERN,
- WAZUH_DEFAULT_APP_CONFIG,
- WAZUH_MONITORING_DEFAULT_CREATION,
- WAZUH_MONITORING_DEFAULT_ENABLED,
- WAZUH_MONITORING_DEFAULT_FREQUENCY,
- WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS,
- WAZUH_MONITORING_DEFAULT_INDICES_SHARDS,
- WAZUH_MONITORING_PATTERN,
- WAZUH_SAMPLE_ALERT_PREFIX,
- WAZUH_STATISTICS_DEFAULT_CREATION,
- WAZUH_STATISTICS_DEFAULT_CRON_FREQ,
- WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS,
- WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS,
- WAZUH_STATISTICS_DEFAULT_NAME,
- WAZUH_STATISTICS_DEFAULT_PREFIX,
- WAZUH_STATISTICS_DEFAULT_STATUS,
+ PLUGIN_SETTINGS_CATEGORIES,
+ TPluginSettingWithKey,
} from '../../common/constants';
+import { getPluginSettingDescription, getSettingsDefaultList, groupSettingsByCategory } from '../../common/services/settings';
import { webDocumentationLink } from '../../common/services/web_documentation';
-import { configEquivalences } from '../../common/config-equivalences';
-/**
- * Given a string, this function builds a multine string, each line about 70
- * characters long, splitted at the closest whitespace character to that lentgh.
- *
- * This function is used to transform the settings description stored in the
- * configEquivalences map into a multiline string to be used as the setting
- * documentation.
- *
- * The # character is also appended to the beginning of each line.
- *
- * @param text
- * @returns multine string
- */
-function splitDescription(text: string = ''): string {
- const lines = text.match(/.{1,80}(?=\s|$)/g) || [];
- return lines.map((z) => '# ' + z.trim()).join('\n');
-}
-
-export const initialWazuhConfig: string = `---
+export const header: string = `---
#
# Wazuh app - App configuration file
# Copyright (C) 2015-2022 Wazuh, Inc.
@@ -61,200 +29,93 @@ export const initialWazuhConfig: string = `---
#
# Find more information about this on the LICENSE file.
#
-# ======================== Wazuh app configuration file ========================
+${printSection('Wazuh app configuration file', {prefix: '# ', fill: '='})}
#
# Please check the documentation for more information about configuration options:
# ${webDocumentationLink('user-manual/wazuh-dashboard/config-file.html')}
#
# Also, you can check our repository:
-# https://github.com/wazuh/wazuh-kibana-app
-#
-# ---------------------------- Unauthorized roles ------------------------------
-#
-# Disable Wazuh for the Elasticsearch / OpenSearch roles defined here.
-# disabled_roles:
-# - wazuh_disabled
-#
-# ------------------------------- Index patterns -------------------------------
-#
-${splitDescription(configEquivalences.pattern)}
-# pattern: ${WAZUH_ALERTS_PATTERN}
-#
-# ----------------------------------- Checks -----------------------------------
-#
-# Define which checks will be executed by the App's HealthCheck.
-# Allowed values are: true, false
-#
-${splitDescription(configEquivalences['checks.pattern'])}
-# checks.pattern: ${WAZUH_DEFAULT_APP_CONFIG['checks.pattern']}
-#
-${splitDescription(configEquivalences['checks.template'])}
-# checks.template: ${WAZUH_DEFAULT_APP_CONFIG['checks.template']}
-#
-${splitDescription(configEquivalences['checks.api'])}
-# checks.api: ${WAZUH_DEFAULT_APP_CONFIG['checks.api']}
-#
-${splitDescription(configEquivalences['checks.setup'])}
-# checks.setup: ${WAZUH_DEFAULT_APP_CONFIG['checks.setup']}
-#
-${splitDescription(configEquivalences['checks.fields'])}
-# checks.fields: ${WAZUH_DEFAULT_APP_CONFIG['checks.fields']}
-#
-${splitDescription(configEquivalences['checks.metaFields'])}
-# checks.metaFields: ${WAZUH_DEFAULT_APP_CONFIG['checks.metaFields']}
-#
-${splitDescription(configEquivalences['checks.timeFilter'])}
-# checks.timeFilter: ${WAZUH_DEFAULT_APP_CONFIG['checks.timeFilter']}
-#
-${splitDescription(configEquivalences['checks.maxBuckets'])}
-# checks.maxBuckets: ${WAZUH_DEFAULT_APP_CONFIG['checks.maxBuckets']}
-#
-# --------------------------------- Extensions ---------------------------------
-#
-# Define the initial state of the extensions (enabled / disabled) for recently
-# added hosts. The extensions can be enabled or disabled anytime using the UI.
-# Allowed values are: true, false
-#
-${splitDescription(configEquivalences['extensions.pci'])}
-# extensions.pci: ${WAZUH_DEFAULT_APP_CONFIG['extensions.pci']}
-#
-${splitDescription(configEquivalences['extensions.gdpr'])}
-# extensions.gdpr: ${WAZUH_DEFAULT_APP_CONFIG['extensions.gdpr']}
-#
-${splitDescription(configEquivalences['extensions.hipaa'])}
-# extensions.hipaa: ${WAZUH_DEFAULT_APP_CONFIG['extensions.hipaa']}
-#
-${splitDescription(configEquivalences['extensions.nist'])}
-# extensions.nist: ${WAZUH_DEFAULT_APP_CONFIG['extensions.nist']}
-#
-${splitDescription(configEquivalences['extensions.tsc'])}
-# extensions.tsc: ${WAZUH_DEFAULT_APP_CONFIG['extensions.tsc']}
-#
-${splitDescription(configEquivalences['extensions.audit'])}
-# extensions.audit: ${WAZUH_DEFAULT_APP_CONFIG['extensions.audit']}
-#
-${splitDescription(configEquivalences['extensions.oscap'])}
-# extensions.oscap: ${WAZUH_DEFAULT_APP_CONFIG['extensions.oscap']}
-#
-${splitDescription(configEquivalences['extensions.ciscat'])}
-# extensions.ciscat: ${WAZUH_DEFAULT_APP_CONFIG['extensions.ciscat']}
-#
-${splitDescription(configEquivalences['extensions.aws'])}
-# extensions.aws: ${WAZUH_DEFAULT_APP_CONFIG['extensions.aws']}
-#
-${splitDescription(configEquivalences['extensions.gcp'])}
-# extensions.gcp: ${WAZUH_DEFAULT_APP_CONFIG['extensions.gcp']}
-#
-${splitDescription(configEquivalences['extensions.virustotal'])}
-# extensions.virustotal: ${WAZUH_DEFAULT_APP_CONFIG['extensions.virustotal']}
-#
-${splitDescription(configEquivalences['extensions.osquery'])}
-# extensions.osquery: ${WAZUH_DEFAULT_APP_CONFIG['extensions.osquery']}
-#
-${splitDescription(configEquivalences['extensions.docker'])}
-# extensions.docker: ${WAZUH_DEFAULT_APP_CONFIG['extensions.docker']}
-#
-# ------------------------------- Timeout --------------------------------------
-#
-${splitDescription(configEquivalences.timeout)}
-# timeout: ${WAZUH_DEFAULT_APP_CONFIG.timeout}
-#
-# --------------------------- Index pattern selector ---------------------------
-#
-${splitDescription(configEquivalences['ip.selector'])}
-# ip.selector: ${WAZUH_DEFAULT_APP_CONFIG['ip.selector']}
-#
-${splitDescription(configEquivalences['ip.ignore'])}
-# ip.ignore: ${WAZUH_DEFAULT_APP_CONFIG['ip.ignore']}
-#
-# ------------------------------ Monitoring ------------------------------------
-#
-${splitDescription(configEquivalences['wazuh.monitoring.enabled'])}
-# wazuh.monitoring.enabled: ${WAZUH_MONITORING_DEFAULT_ENABLED}
-#
-${splitDescription(configEquivalences['wazuh.monitoring.frequency'])}
-# wazuh.monitoring.frequency: ${WAZUH_MONITORING_DEFAULT_FREQUENCY}
-#
-${splitDescription(configEquivalences['wazuh.monitoring.shards'])}
-# wazuh.monitoring.shards: ${WAZUH_MONITORING_DEFAULT_INDICES_SHARDS}
-#
-${splitDescription(configEquivalences['wazuh.monitoring.replicas'])}
-# wazuh.monitoring.replicas: ${WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS}
-#
-${splitDescription(configEquivalences['wazuh.monitoring.creation'])}
-# Allowed values are: h (hourly), d (daily), w (weekly), m (monthly)
-# wazuh.monitoring.creation: ${WAZUH_MONITORING_DEFAULT_CREATION}
-#
-${splitDescription(configEquivalences['wazuh.monitoring.pattern'])}
-# wazuh.monitoring.pattern: ${WAZUH_MONITORING_PATTERN}
-#
-# --------------------------------- Sample data --------------------------------
-#
-${splitDescription(configEquivalences['alerts.sample.prefix'])}
-# alerts.sample.prefix: ${WAZUH_SAMPLE_ALERT_PREFIX}
-#
-# ------------------------------ Background tasks ------------------------------
-#
-${splitDescription(configEquivalences['cron.prefix'])}
-# cron.prefix: ${WAZUH_STATISTICS_DEFAULT_PREFIX}
-#
-# ------------------------------ Wazuh Statistics ------------------------------
-#
-${splitDescription(configEquivalences['cron.statistics.status'])}
-# cron.statistics.status: ${WAZUH_STATISTICS_DEFAULT_STATUS}
-#
-${splitDescription(configEquivalences['cron.statistics.apis'])}
-# cron.statistics.apis: ${WAZUH_DEFAULT_APP_CONFIG['cron.statistics.apis']}
-#
-${splitDescription(configEquivalences['cron.statistics.interval'])}
-# cron.statistics.interval: ${WAZUH_STATISTICS_DEFAULT_CRON_FREQ}
-#
-${splitDescription(configEquivalences['cron.statistics.index.name'])}
-# cron.statistics.index.name: ${WAZUH_STATISTICS_DEFAULT_NAME}
-#
-${splitDescription(configEquivalences['cron.statistics.index.creation'])}
-# cron.statistics.index.creation: ${WAZUH_STATISTICS_DEFAULT_CREATION}
-#
-${splitDescription(configEquivalences['cron.statistics.index.shards'])}
-# cron.statistics.shards: ${WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS}
-#
-${splitDescription(configEquivalences['cron.statistics.index.replicas'])}
-# cron.statistics.replicas: ${WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS}
-#
-# ------------------------------ Logo customization ----------------------------
-#
-${splitDescription(configEquivalences['customization.logo.app'])}
-# customization.logo.app: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.app']}
-#
-${splitDescription(configEquivalences['customization.logo.sidebar'])}
-# customization.logo.sidebar: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.sidebar']}
-#
-${splitDescription(configEquivalences['customization.logo.healthcheck'])}
-# customization.logo.healthcheck: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.healthcheck']}
-#
-${splitDescription(configEquivalences['customization.logo.reports'])}
-# customization.logo.reports: ${WAZUH_DEFAULT_APP_CONFIG['customization.logo.reports']}
-#
-# ---------------------------- Hide manager alerts -----------------------------
-#
-${splitDescription(configEquivalences.hideManagerAlerts)}
-# hideManagerAlerts: ${WAZUH_DEFAULT_APP_CONFIG.hideManagerAlerts}
-#
-# ------------------------------- App logging level ----------------------------
-#
-${splitDescription(configEquivalences['logs.level'])}
-# Allowed values are: info, debug
-# logs.level: ${WAZUH_DEFAULT_APP_CONFIG['logs.level']}
-#
-# ------------------------------- Agent enrollment -----------------------------
-#
-${splitDescription(configEquivalences['enrollment.dns'])}
-# enrollment.dns: ${WAZUH_DEFAULT_APP_CONFIG['enrollment.dns']}
-#
-${splitDescription(configEquivalences['enrollment.password'])}
-# enrollment.password: ${WAZUH_DEFAULT_APP_CONFIG['enrollment.password']}
-#
-#-------------------------------- Wazuh hosts ----------------------------------
+# https://github.com/wazuh/wazuh-kibana-app`;
+
+const pluginSettingsConfigurationFile = getSettingsDefaultList().filter(({isConfigurableFromFile}) => isConfigurableFromFile);
+
+const pluginSettingsConfigurationFileGroupByCategory = groupSettingsByCategory(pluginSettingsConfigurationFile);
+
+const pluginSettingsConfiguration = pluginSettingsConfigurationFileGroupByCategory.map(({category: categoryID, settings}) => {
+ const category = printSettingCategory(PLUGIN_SETTINGS_CATEGORIES[categoryID]);
+
+ const pluginSettingsOfCategory = settings
+ .map(setting => printSetting(setting)
+ ).join('\n#\n');
+ /*
+ #------------------- {category name} --------------
+ #
+ # {category description}
+ #
+ # {setting description}
+ # settingKey: settingDefaultValue
+ #
+ # {setting description}
+ # settingKey: settingDefaultValue
+ # ...
+ */
+ return [category, pluginSettingsOfCategory].join('\n#\n');
+}).join('\n#\n');
+
+
+export function printSettingValue(value: unknown): any{
+ if(typeof value === 'object'){
+ return JSON.stringify(value)
+ };
+
+ if(typeof value === 'string' && value.length === 0){
+ return `''`
+ };
+
+ return value;
+};
+
+export function printSetting(setting: TPluginSettingWithKey): string{
+ /*
+ # {setting description}
+ # {settingKey}: {settingDefaultValue}
+ */
+ return [
+ splitDescription(getPluginSettingDescription(setting)),
+ `# ${setting.key}: ${printSettingValue(setting.defaultValue)}`
+ ].join('\n')
+}
+
+export function printSettingCategory({title, description}){
+ /*
+ #------------------------------- {category title} -------------------------------
+ # {category description}
+ #
+ */
+ return [
+ printSection(title, {prefix: '# ', fill: '-'}),
+ ...(description ? [splitDescription(description)] : [''])
+ ].join('\n#\n')
+};
+
+export function printSection(text: string, options?: {maxLength?: number, prefix?: string, suffix?: string, spaceAround?: number, fill?: string }){
+ const maxLength = options?.maxLength ?? 80;
+ const prefix = options?.prefix ?? '';
+ const sufix = options?.suffix ?? '';
+ const spaceAround = options?.spaceAround ?? 1;
+ const fill = options?.fill ?? ' ';
+ const fillLength = maxLength - prefix.length - sufix.length - (2 * spaceAround) - text.length;
+
+ return [
+ prefix,
+ fill.repeat(Math.floor(fillLength/2)),
+ ` ${text} `,
+ fill.repeat(Math.ceil(fillLength/2)),
+ sufix
+ ].join('');
+};
+
+export const hostsConfiguration = `${printSection('Wazuh hosts', {prefix: '# ', fill: '-'})}
#
# The following configuration is the default structure to define a host.
#
@@ -286,3 +147,22 @@ hosts:
password: wazuh-wui
run_as: false
`;
+
+/**
+ * Given a string, this function builds a multine string, each line about 70
+ * characters long, splitted at the closest whitespace character to that lentgh.
+ *
+ * This function is used to transform the settings description
+ * into a multiline string to be used as the setting documentation.
+ *
+ * The # character is also appended to the beginning of each line.
+ *
+ * @param text
+ * @returns multine string
+ */
+export function splitDescription(text: string = ''): string {
+ const lines = text.match(/.{1,80}(?=\s|$)/g) || [];
+ return lines.map((z) => '# ' + z.trim()).join('\n');
+}
+
+export const initialWazuhConfig: string = [header, pluginSettingsConfiguration, hostsConfiguration].join('\n#\n');
diff --git a/server/lib/reporting/audit-request.ts b/server/lib/reporting/audit-request.ts
index cb67d0b192..6de588968e 100644
--- a/server/lib/reporting/audit-request.ts
+++ b/server/lib/reporting/audit-request.ts
@@ -11,7 +11,7 @@
*/
import { Base } from './base-query';
import AuditMap from './audit-map';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 3 agents that execute sudo commands without success
@@ -26,7 +26,7 @@ export const getTop3AgentsSudoNonSuccessful = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -93,7 +93,7 @@ export const getTop3AgentsFailedSyscalls = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -172,7 +172,7 @@ export const getTopFailedSyscalls = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
diff --git a/server/lib/reporting/extended-information.ts b/server/lib/reporting/extended-information.ts
index 6bec747cc7..a4c27ec9a1 100644
--- a/server/lib/reporting/extended-information.ts
+++ b/server/lib/reporting/extended-information.ts
@@ -12,9 +12,9 @@ import * as SyscheckRequest from './syscheck-request';
import PCI from '../../integration-files/pci-requirements-pdfmake';
import GDPR from '../../integration-files/gdpr-requirements-pdfmake';
import TSC from '../../integration-files/tsc-requirements-pdfmake';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
import { ReportPrinter } from './printer';
import moment from 'moment';
+import { getSettingDefaultValue } from '../../../common/services/settings';
@@ -131,7 +131,7 @@ export async function extendedInformation(
from,
to,
filters,
- pattern = WAZUH_ALERTS_PATTERN,
+ pattern = getSettingDefaultValue('pattern'),
agent = null
) {
try {
diff --git a/server/lib/reporting/gdpr-request.ts b/server/lib/reporting/gdpr-request.ts
index c6cc5f97cb..2368984b0f 100644
--- a/server/lib/reporting/gdpr-request.ts
+++ b/server/lib/reporting/gdpr-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 5 GDPR requirements
@@ -25,7 +25,7 @@ export const topGDPRRequirements = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.gdpr: exists')) {
const [head, tail] = filters.split('AND rule.gdpr: exists');
@@ -82,7 +82,7 @@ export const getRulesByRequirement= async (
lte,
filters,
requirement,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.gdpr: exists')) {
const [head, tail] = filters.split('AND rule.gdpr: exists');
diff --git a/server/lib/reporting/overview-request.ts b/server/lib/reporting/overview-request.ts
index 748ceffd3b..0a75268e70 100644
--- a/server/lib/reporting/overview-request.ts
+++ b/server/lib/reporting/overview-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 3 agents with level 15 alerts
@@ -20,7 +20,7 @@ import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
* @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
* @returns {Array} E.g:['000','130','300']
*/
-export const topLevel15 = async (context, gte, lte, filters, pattern = WAZUH_ALERTS_PATTERN) => {
+export const topLevel15 = async (context, gte, lte, filters, pattern = getSettingDefaultValue('pattern')) => {
try {
const base = {};
diff --git a/server/lib/reporting/pci-request.ts b/server/lib/reporting/pci-request.ts
index e579532d89..a4ac21e1c4 100644
--- a/server/lib/reporting/pci-request.ts
+++ b/server/lib/reporting/pci-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 5 PCI DSS requirements
@@ -25,7 +25,7 @@ export const topPCIRequirements = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.pci_dss: exists')) {
filters = filters.replace('AND rule.pci_dss: exists', '');
@@ -96,7 +96,7 @@ export const getRulesByRequirement = async (
lte,
filters,
requirement,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.pci_dss: exists')) {
filters = filters.replace('AND rule.pci_dss: exists', '');
diff --git a/server/lib/reporting/rootcheck-request.ts b/server/lib/reporting/rootcheck-request.ts
index c7d4cdd510..ba41e694df 100644
--- a/server/lib/reporting/rootcheck-request.ts
+++ b/server/lib/reporting/rootcheck-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 5 rootkits found along all agents
@@ -25,7 +25,7 @@ export const top5RootkitsDetected = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN,
+ pattern = getSettingDefaultValue('pattern'),
size = 5
) => {
try {
@@ -80,7 +80,7 @@ export const agentsWithHiddenPids = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -129,7 +129,7 @@ export const agentsWithHiddenPorts = async(
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
diff --git a/server/lib/reporting/summary-table.ts b/server/lib/reporting/summary-table.ts
index fb60099eb6..f2e1ddb766 100644
--- a/server/lib/reporting/summary-table.ts
+++ b/server/lib/reporting/summary-table.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
interface SummarySetup {
title: string;
@@ -24,7 +24,7 @@ export default class SummaryTable {
lte,
filters,
summarySetup: SummarySetup,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) {
this._context = context;
diff --git a/server/lib/reporting/syscheck-request.ts b/server/lib/reporting/syscheck-request.ts
index 9016a93ee6..3edc293cb0 100644
--- a/server/lib/reporting/syscheck-request.ts
+++ b/server/lib/reporting/syscheck-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
@@ -26,7 +26,7 @@ export const top3agents = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -78,7 +78,7 @@ export const top3Rules = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -137,7 +137,7 @@ export const lastTenDeletedFiles = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -190,7 +190,7 @@ export const lastTenModifiedFiles = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
diff --git a/server/lib/reporting/tsc-request.ts b/server/lib/reporting/tsc-request.ts
index 753258a38e..4601142f67 100644
--- a/server/lib/reporting/tsc-request.ts
+++ b/server/lib/reporting/tsc-request.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
+import { getSettingDefaultValue } from '../../../common/services/settings';
/**
* Returns top 5 TSC requirements
@@ -25,7 +25,7 @@ export const topTSCRequirements = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.tsc: exists')) {
filters = filters.replace('AND rule.tsc: exists', '');
@@ -96,7 +96,7 @@ export const getRulesByRequirement = async (
lte,
filters,
requirement,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
if (filters.includes('rule.tsc: exists')) {
filters = filters.replace('AND rule.tsc: exists', '');
diff --git a/server/lib/reporting/vulnerability-request.ts b/server/lib/reporting/vulnerability-request.ts
index c5e1c62d4b..bd3596a1bb 100644
--- a/server/lib/reporting/vulnerability-request.ts
+++ b/server/lib/reporting/vulnerability-request.ts
@@ -9,8 +9,8 @@
*
* Find more information about this on the LICENSE file.
*/
+import { getSettingDefaultValue } from '../../../common/services/settings';
import { Base } from './base-query';
-import { WAZUH_ALERTS_PATTERN } from '../../../common/constants';
/**
* Returns top 3 agents for specific severity
@@ -27,7 +27,7 @@ export const topAgentCount = async (
lte,
severity,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -79,7 +79,7 @@ export const topCVECount = async (
gte,
lte,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -125,7 +125,7 @@ export const uniqueSeverityCount = async (
lte,
severity,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -170,7 +170,7 @@ export const topPackages = async (
lte,
severity,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
@@ -219,7 +219,7 @@ export const topPackagesWithCVE = async(
lte,
severity,
filters,
- pattern = WAZUH_ALERTS_PATTERN
+ pattern = getSettingDefaultValue('pattern')
) => {
try {
const base = {};
diff --git a/server/lib/update-configuration.ts b/server/lib/update-configuration.ts
index 67bb9be148..0c9adb84d2 100644
--- a/server/lib/update-configuration.ts
+++ b/server/lib/update-configuration.ts
@@ -12,7 +12,8 @@
import fs from 'fs';
import { log } from './logger';
import { getConfiguration } from './get-configuration';
-import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART, WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD, WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK } from '../../common/constants';
+import { WAZUH_DATA_CONFIG_APP_PATH } from '../../common/constants';
+import { formatSettingValueToFile } from '../../common/services/settings';
export class UpdateConfigurationFile {
constructor() {
@@ -30,7 +31,7 @@ export class UpdateConfigurationFile {
try {
const data = fs.readFileSync(this.file, { encoding: 'utf-8' });
const re = new RegExp(`^${key}\\s{0,}:\\s{1,}.*`, 'gm');
- const formatedValue = this.formatValue(value);
+ const formatedValue = formatSettingValueToFile(value);
const result = exists
? data.replace(re, `${key}: ${formatedValue}`)
: `${data}\n${key}: ${formatedValue}`;
@@ -43,33 +44,26 @@ export class UpdateConfigurationFile {
}
}
- formatValue = (value) => typeof value === 'string'
- ? isNaN(Number(value)) ? `'${value}'` : value
- : typeof value === 'object'
- ? JSON.stringify(value)
- : value
-
- formatValueCachedConfiguration = (value) => typeof value === 'string'
- ? isNaN(Number(value)) ? value : Number(value)
- : value;
/**
* Updates wazuh.yml file. If it fails, it throws the error to the next function.
- * @param {Object} input
+ * @param {Object} updatedConfiguration
*/
- updateConfiguration(input) {
+ updateConfiguration(updatedConfiguration) {
try {
if (this.busy) {
throw new Error('Another process is updating the configuration file');
}
this.busy = true;
- const configuration = getConfiguration(true) || {};
-
- const { key, value } = (input || {}).body || {};
- this.updateLine(key, value, typeof configuration[key] !== 'undefined');
+ const pluginConfiguration = getConfiguration({force: true}) || {};
- // Update the app configuration server-cached setting in memory with the new value
- configuration[key] = this.formatValueCachedConfiguration(value);
+ for(const pluginSettingKey in updatedConfiguration){
+ // Store the configuration in the configuration file.
+ const value = updatedConfiguration[pluginSettingKey];
+ this.updateLine(pluginSettingKey, value, typeof pluginConfiguration[pluginSettingKey] !== 'undefined');
+ // Update the app configuration server-cached setting in memory with the new value.
+ pluginConfiguration[pluginSettingKey] = value;
+ };
this.busy = false;
log(
@@ -77,11 +71,6 @@ export class UpdateConfigurationFile {
'Updating configuration',
'debug'
);
- return {
- needRestart: WAZUH_CONFIGURATION_SETTINGS_NEED_RESTART.includes(key),
- needReload: WAZUH_CONFIGURATION_SETTINGS_NEED_RELOAD.includes(key),
- needHealtCheck: WAZUH_CONFIGURATION_SETTINGS_NEED_HEALTH_CHECK.includes(key)
- };
} catch (error) {
log('update-configuration:updateConfiguration', error.message || error);
this.busy = false;
diff --git a/server/routes/wazuh-utils/wazuh-utils.test.ts b/server/routes/wazuh-utils/wazuh-utils.test.ts
index 166e5331a9..55d9ab493d 100644
--- a/server/routes/wazuh-utils/wazuh-utils.test.ts
+++ b/server/routes/wazuh-utils/wazuh-utils.test.ts
@@ -1,76 +1,172 @@
-// To launch this file
+// To run this file:
// yarn test:jest --testEnvironment node --verbose server/routes/wazuh-utils
-import axios from 'axios';
-import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../../common/constants';
-
-function buildAxiosOptions(method: string, path: string, data: any = {}, headers: any = {}) {
- return {
- method: method,
- headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json', ...headers },
- url: `http://localhost:5601${path}`,
- data: data,
- };
-}
-
-describe.skip('Wazuh API - utils', () => {
- describe('Wazuh API - /utils/configuration', () => {
- test('[200] Returns the app configuration', () => {
- const options = buildAxiosOptions('get', '/utils/configuration');
- return axios(options)
- .then((response) => {
- expect(response.status).toBe(200);
- expect(typeof response.data.data).toBe('object');
- expect(typeof response.data.data.hosts).toBe('object');
- })
- .catch((error) => {
- throw error;
- });
- });
+import { Router } from '../../../../../src/core/server/http/router/router';
+import { HttpServer } from '../../../../../src/core/server/http/http_server';
+import { loggingSystemMock } from '../../../../../src/core/server/logging/logging_system.mock';
+import { ByteSizeValue } from '@kbn/config-schema';
+import supertest from 'supertest';
+import { WazuhUtilsRoutes } from './wazuh-utils';
+import { WazuhUtilsCtrl } from '../../controllers/wazuh-utils/wazuh-utils';
+import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../../lib/filesystem';
+import { WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, WAZUH_DATA_LOGS_DIRECTORY_PATH, WAZUH_DATA_LOGS_RAW_PATH } from '../../../common/constants';
+import { execSync } from 'child_process';
+import fs from 'fs';
+
+const loggingService = loggingSystemMock.create();
+const logger = loggingService.get();
+const context = {
+ wazuh: {
+ }
+};
+
+const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context);
+let server, innerServer;
+
+beforeAll(async () => {
+ // Create /data/wazuh directory.
+ createDataDirectoryIfNotExists();
+ // Create /data/wazuh/config directory.
+ createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH);
+
+ // Create /data/wazuh/logs directory.
+ createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH);
+
+ // Create server
+ const config = {
+ name: 'plugin_platform',
+ host: '127.0.0.1',
+ maxPayload: new ByteSizeValue(1024),
+ port: 10002,
+ ssl: { enabled: false },
+ compression: { enabled: true },
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
+ } as any;
+ server = new HttpServer(loggingService, 'tests');
+ const router = new Router('', logger, enhanceWithContext);
+ const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config);
+ innerServer = innerServerTest;
+
+ const spyRouteDecoratorProtectedAdministratorRoleValidToken = jest.spyOn(WazuhUtilsCtrl.prototype as any, 'routeDecoratorProtectedAdministratorRoleValidToken')
+ .mockImplementation((handler) => async (...args) => handler(...args));
+
+ // Register routes
+ WazuhUtilsRoutes(router);
+
+ // Register router
+ registerRouter(router);
+
+ // start server
+ await server.start();
+});
+
+afterAll(async () => {
+ // Stop server
+ await server.stop();
+
+ // Clear all mocks
+ jest.clearAllMocks();
+
+ // Remove /data/wazuh directory.
+ execSync(`rm -rf ${WAZUH_DATA_ABSOLUTE_PATH}`);
+});
+
+describe('[endpoint] GET /utils/configuration', () => {
+ beforeAll(() => {
+ // Create the configuration file with custom content
+ const fileContent = `---
+pattern: test-alerts-*
+
+hosts:
+ - default:
+ url: https://localhost
+ port: 55000
+ username: wazuh-wui
+ password: wazuh-wui
+ run_as: false
+`;
+
+ fs.writeFileSync(WAZUH_DATA_CONFIG_APP_PATH, fileContent, 'utf8');
+ });
+
+ afterAll(() => {
+ // Remove the configuration file
+ fs.unlinkSync(WAZUH_DATA_CONFIG_APP_PATH);
+ });
+
+ it(`Get plugin configuration GET /utils/configuration - 200`, async () => {
+ const response = await supertest(innerServer.listener)
+ .get('/utils/configuration')
+ .expect(200);
+ expect(response.body.data).toBeDefined();
+ expect(response.body.data.pattern).toBeDefined();
+ expect(response.body.data.hosts).toBeDefined();
+ });
+});
+
+
+describe('[endpoint] PUT /utils/configuration', () => {
+ beforeAll(() => {
+ // Create the configuration file with custom content
+ const fileContent = `---
+pattern: test-alerts-*
+
+hosts:
+ - default:
+ url: https://localhost
+ port: 55000
+ username: wazuh-wui
+ password: wazuh-wui
+ run_as: false
+`;
+
+ fs.writeFileSync(WAZUH_DATA_CONFIG_APP_PATH, fileContent, 'utf8');
});
- describe('Wazuh API - /utils/configuration', () => {
- let userToken = null;
- beforeAll(() => {
- const optionsAuthenticate = buildAxiosOptions('post', '/api/login', {
- idHost: 'default',
- });
- return axios(optionsAuthenticate).then((response) => {
- userToken = response.data.token;
- return response.data.token;
- });
- });
- test('[200] Updates the app configuration', () => {
- const options = buildAxiosOptions(
- 'put',
- '/utils/configuration',
- {
- key: 'logs.level',
- value: 'debug',
- },
- {
- cookie: `wz-token=${userToken};wz-api=default;`,
- }
- );
- return axios(options)
- .then((response) => {
- expect(response.status).toBe(200);
- })
- .catch((error) => {
- throw error;
- });
- });
+ afterAll(() => {
+ // Remove the configuration file
+ fs.unlinkSync(WAZUH_DATA_CONFIG_APP_PATH);
});
- describe('Wazuh API - /utils/logs', () => {
- test('[200] Get the app logs', () => {
- const options = buildAxiosOptions('get', '/utils/logs');
- return axios(options)
- .then((response) => {
- expect(response.status).toBe(200);
- })
- .catch((error) => {
- throw error;
- });
- });
+ it.each`
+ settings | responseStatusCode
+ ${{pattern: 'test-alerts-groupA-*'}} | ${200}
+ ${{pattern: 'test-alerts-groupA-*','logs.level': 'debug'}} | ${200}
+ `(`Update the plugin configuration: $settings. PUT /utils/configuration - $responseStatusCode`, async ({responseStatusCode, settings}) => {
+ const response = await supertest(innerServer.listener)
+ .put('/utils/configuration')
+ .send(settings)
+ .expect(responseStatusCode);
+
+ expect(response.body.data.updatedConfiguration).toEqual(settings);
+ expect(response.body.data.requiresRunningHealthCheck).toBeDefined();
+ expect(response.body.data.requiresReloadingBrowserTab).toBeDefined();
+ expect(response.body.data.requiresRestartingPluginPlatform).toBeDefined();
+ });
+});
+
+describe('[endpoint] GET /utils/logs', () => {
+ beforeAll(() => {
+ // Create the configuration file with custom content
+ const fileContent = `---
+{"date":"2022-09-20T08:36:16.688Z","level":"info","location":"initialize","message":"Kibana index: .kibana"}
+{"date":"2022-09-20T08:36:16.689Z","level":"info","location":"initialize","message":"App revision: 4400"}
+`;
+ fs.writeFileSync(WAZUH_DATA_LOGS_RAW_PATH, fileContent, 'utf8');
+ });
+
+ afterAll(() => {
+ // Remove the configuration file
+ fs.unlinkSync(WAZUH_DATA_LOGS_RAW_PATH);
+ });
+
+ it(`Get plugin logs. GET /utils/logs`, async () => {
+ const response = await supertest(innerServer.listener)
+ .get('/utils/logs')
+ .expect(200);
+
+ expect(response.body.lastLogs).toBeDefined();
});
});
diff --git a/server/routes/wazuh-utils/wazuh-utils.ts b/server/routes/wazuh-utils/wazuh-utils.ts
index 30ba0b0992..d1a31b4377 100644
--- a/server/routes/wazuh-utils/wazuh-utils.ts
+++ b/server/routes/wazuh-utils/wazuh-utils.ts
@@ -30,10 +30,7 @@ export function WazuhUtilsRoutes(router: IRouter) {
{
path: '/utils/configuration',
validate: {
- body: schema.object({
- key: schema.string(),
- value: schema.any()
- })
+ body: schema.any()
}
},
async (context, request, response) => ctrl.updateConfigurationFile(context, request, response)
diff --git a/server/start/cron-scheduler/scheduler-handler.ts b/server/start/cron-scheduler/scheduler-handler.ts
index 25515fb296..242e8d80e3 100644
--- a/server/start/cron-scheduler/scheduler-handler.ts
+++ b/server/start/cron-scheduler/scheduler-handler.ts
@@ -3,9 +3,10 @@ import { configuredJobs } from './configured-jobs';
import { log } from '../../lib/logger';
import { getConfiguration } from '../../lib/get-configuration';
import cron from 'node-cron';
-import { WAZUH_STATISTICS_DEFAULT_PREFIX, WAZUH_STATISTICS_DEFAULT_NAME, WAZUH_STATISTICS_TEMPLATE_NAME } from '../../../common/constants';
+import { WAZUH_STATISTICS_TEMPLATE_NAME } from '../../../common/constants';
import { statisticsTemplate } from '../../integration-files/statistics-template';
import { delayAsPromise } from '../../../common/utils';
+import { getSettingDefaultValue } from '../../../common/services/settings';
const blueWazuh = '\u001b[34mwazuh\u001b[39m';
const schedulerErrorLogColors = [blueWazuh, 'scheduler', 'error'];
@@ -67,8 +68,8 @@ const checkTemplate = async function (context) {
);
const appConfig = await getConfiguration();
- const prefixTemplateName = appConfig['cron.prefix'] || WAZUH_STATISTICS_DEFAULT_PREFIX;
- const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name'] || WAZUH_STATISTICS_DEFAULT_NAME;
+ const prefixTemplateName = appConfig['cron.prefix'] || getSettingDefaultValue('cron.prefix');
+ const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name'] || getSettingDefaultValue('cron.statistics.index.name');
const pattern = `${prefixTemplateName}-${statisticsIndicesTemplateName}-*`;
try {
diff --git a/server/start/initialize/index.ts b/server/start/initialize/index.ts
index 51281f5112..157b279706 100644
--- a/server/start/initialize/index.ts
+++ b/server/start/initialize/index.ts
@@ -15,9 +15,10 @@ import { pluginPlatformTemplate } from '../../integration-files/kibana-template'
import { getConfiguration } from '../../lib/get-configuration';
import { totalmem } from 'os';
import fs from 'fs';
-import { WAZUH_ALERTS_PATTERN, WAZUH_DATA_CONFIG_REGISTRY_PATH, WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, PLUGIN_PLATFORM_NAME, PLUGIN_PLATFORM_INSTALLATION_USER_GROUP, PLUGIN_PLATFORM_INSTALLATION_USER, WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants';
+import { WAZUH_ALERTS_PATTERN, WAZUH_DATA_CONFIG_REGISTRY_PATH, WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, PLUGIN_PLATFORM_NAME, PLUGIN_PLATFORM_INSTALLATION_USER_GROUP, PLUGIN_PLATFORM_INSTALLATION_USER, WAZUH_DEFAULT_APP_CONFIG, PLUGIN_APP_NAME } from '../../../common/constants';
import { createDataDirectoryIfNotExists } from '../../lib/filesystem';
import _ from 'lodash';
+import { getSettingDefaultValue, getSettingsDefault } from '../../../common/services/settings';
export function jobInitializeRun(context) {
@@ -34,7 +35,7 @@ export function jobInitializeRun(context) {
pattern =
configurationFile && typeof configurationFile.pattern !== 'undefined'
? configurationFile.pattern
- : WAZUH_ALERTS_PATTERN;
+ : getSettingDefaultValue('pattern');
} catch (error) {
log('initialize', error.message || error);
context.wazuh.logger.error(
@@ -107,7 +108,7 @@ export function jobInitializeRun(context) {
'debug'
);
- if(!fs.existsSync(WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH)){
+ if (!fs.existsSync(WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH)) {
throw new Error(`The data directory is missing in the ${PLUGIN_PLATFORM_NAME} root instalation. Create the directory in ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH} and give it the required permissions (sudo mkdir ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH};sudo chown -R ${PLUGIN_PLATFORM_INSTALLATION_USER}:${PLUGIN_PLATFORM_INSTALLATION_USER_GROUP} ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH}). After restart the ${PLUGIN_PLATFORM_NAME} service.`);
};
@@ -128,7 +129,7 @@ export function jobInitializeRun(context) {
const isUpgradedApp = packageJSON.revision !== source.revision || packageJSON.version !== source['app-version'];
// Rebuild the registry file if revision or version fields are differents
- if (isUpgradedApp) {
+ if (isUpgradedApp) {
log(
'initialize:checkwazuhRegistry',
'Wazuh app revision or version changed, regenerating wazuh-registry.json.',
@@ -138,11 +139,11 @@ export function jobInitializeRun(context) {
// Rebuild the registry file `wazuh-registry.json`
// Get the supported extensions for the installed plugin
- const supportedDefaultExtensionsConfiguration = Object.entries(WAZUH_DEFAULT_APP_CONFIG)
+ const supportedDefaultExtensionsConfiguration = Object.entries(getSettingsDefault())
.filter(([setting]) => setting.startsWith('extensions.'))
.map(([setting, settingValue]) => {
return [setting.split('.')[1], settingValue];
- });
+ });
// Get the supported extensions by ID
const supportedDefaultExtensionsNames = supportedDefaultExtensionsConfiguration.map(([setting]) => setting);
@@ -153,7 +154,7 @@ export function jobInitializeRun(context) {
// Remove the unsupported extensions for the installed plugin
const registryHostsData = Object.entries(source.hosts).reduce((accum, [hostID, hostData]) => {
accum[hostID] = hostData;
- if(accum[hostID].extensions){
+ if (accum[hostID].extensions) {
// Migrate extensions to those supported by the installed plugin
const defaultHostExtentionsConfiguration = Object.fromEntries(supportedDefaultExtensionsConfiguration);
// Select of current configuration the extension IDs that are supported in the installed plugin
@@ -224,8 +225,7 @@ export function jobInitializeRun(context) {
} catch (error) {
return Promise.reject(
new Error(
- `Error creating ${
- PLUGIN_PLATFORM_INDEX
+ `Error creating ${PLUGIN_PLATFORM_INDEX
} index due to ${error.message || error}`
)
);
@@ -244,8 +244,7 @@ export function jobInitializeRun(context) {
} catch (error) {
return Promise.reject(
new Error(
- `Error creating template for ${
- PLUGIN_PLATFORM_INDEX
+ `Error creating template for ${PLUGIN_PLATFORM_INDEX
} due to ${error.message || error}`
)
);
diff --git a/server/start/monitoring/index.ts b/server/start/monitoring/index.ts
index e5e43853c8..a3c959800b 100644
--- a/server/start/monitoring/index.ts
+++ b/server/start/monitoring/index.ts
@@ -18,16 +18,11 @@ import { indexDate } from '../../lib/index-date';
import { buildIndexSettings } from '../../lib/build-index-settings';
import { WazuhHostsCtrl } from '../../controllers/wazuh-hosts';
import {
- WAZUH_MONITORING_PATTERN,
WAZUH_MONITORING_TEMPLATE_NAME,
- WAZUH_MONITORING_DEFAULT_CREATION,
- WAZUH_MONITORING_DEFAULT_ENABLED,
- WAZUH_MONITORING_DEFAULT_FREQUENCY,
- WAZUH_MONITORING_DEFAULT_INDICES_SHARDS,
- WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS,
} from '../../../common/constants';
import { tryCatchForIndexPermissionError } from '../tryCatchForIndexPermissionError';
import { delayAsPromise } from '../../../common/utils';
+import { getSettingDefaultValue } from '../../../common/services/settings';
const blueWazuh = '\u001b[34mwazuh\u001b[39m';
const monitoringErrorLogColors = [blueWazuh, 'monitoring', 'error'];
@@ -56,12 +51,12 @@ function initMonitoringConfiguration(context){
MONITORING_ENABLED = appConfig && typeof appConfig['wazuh.monitoring.enabled'] !== 'undefined'
? appConfig['wazuh.monitoring.enabled'] &&
appConfig['wazuh.monitoring.enabled'] !== 'worker'
- : WAZUH_MONITORING_DEFAULT_ENABLED;
- MONITORING_FREQUENCY = getAppConfigurationSetting('wazuh.monitoring.frequency', appConfig, WAZUH_MONITORING_DEFAULT_FREQUENCY);
+ : getSettingDefaultValue('wazuh.monitoring.enabled');
+ MONITORING_FREQUENCY = getAppConfigurationSetting('wazuh.monitoring.frequency', appConfig, getSettingDefaultValue('wazuh.monitoring.frequency'));
MONITORING_CRON_FREQ = parseCron(MONITORING_FREQUENCY);
- MONITORING_CREATION = getAppConfigurationSetting('wazuh.monitoring.creation', appConfig, WAZUH_MONITORING_DEFAULT_CREATION);
+ MONITORING_CREATION = getAppConfigurationSetting('wazuh.monitoring.creation', appConfig, getSettingDefaultValue('wazuh.monitoring.creation'));
- MONITORING_INDEX_PATTERN = getAppConfigurationSetting('wazuh.monitoring.pattern', appConfig, WAZUH_MONITORING_PATTERN);
+ MONITORING_INDEX_PATTERN = getAppConfigurationSetting('wazuh.monitoring.pattern', appConfig, getSettingDefaultValue('wazuh.monitoring.pattern'));
const lastCharIndexPattern = MONITORING_INDEX_PATTERN[MONITORING_INDEX_PATTERN.length - 1];
if (lastCharIndexPattern !== '*') {
MONITORING_INDEX_PATTERN += '*';
@@ -131,7 +126,7 @@ async function checkTemplate(context) {
monitoringTemplate.index_patterns = currentTemplate.body[WAZUH_MONITORING_TEMPLATE_NAME].index_patterns;
}catch (error) {
// Init with the default index pattern
- monitoringTemplate.index_patterns = [WAZUH_MONITORING_PATTERN];
+ monitoringTemplate.index_patterns = [getSettingDefaultValue('wazuh.monitoring.pattern')];
}
// Check if the user is using a custom pattern and add it to the template if it does
@@ -182,7 +177,7 @@ async function insertMonitoringDataElasticsearch(context, data) {
const indexConfiguration = buildIndexSettings(
appConfig,
'wazuh.monitoring',
- WAZUH_MONITORING_DEFAULT_INDICES_SHARDS
+ getSettingDefaultValue('wazuh.monitoring.shards')
);
// To update the index settings with this client is required close the index, update the settings and open it
@@ -258,8 +253,8 @@ async function createIndex(context, indexName: string) {
const IndexConfiguration = {
settings: {
index: {
- number_of_shards: getAppConfigurationSetting('wazuh.monitoring.shards', appConfig, WAZUH_MONITORING_DEFAULT_INDICES_SHARDS),
- number_of_replicas: getAppConfigurationSetting('wazuh.monitoring.replicas', appConfig, WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS)
+ number_of_shards: getAppConfigurationSetting('wazuh.monitoring.shards', appConfig, getSettingDefaultValue('wazuh.monitoring.shards')),
+ number_of_replicas: getAppConfigurationSetting('wazuh.monitoring.replicas', appConfig, getSettingDefaultValue('wazuh.monitoring.replicas'))
}
}
};
diff --git a/test/functional/apps/overview/_integrity_monitoring.ts b/test/functional/apps/overview/_integrity_monitoring.ts
index 588e83dd4e..8daebf4acd 100644
--- a/test/functional/apps/overview/_integrity_monitoring.ts
+++ b/test/functional/apps/overview/_integrity_monitoring.ts
@@ -13,7 +13,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../../test/functional/ftr_provider_context';
import { SearchParams } from 'elasticsearch';
-import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants';
+import { getSettingDefaultValue } from '../../../../common/services/settings';
export default function({getService, getPageObjects, }: FtrProviderContext) {
const areaChart = getService('areaChart');
@@ -33,7 +33,7 @@ export default function({getService, getPageObjects, }: FtrProviderContext) {
let es_index: string;
before(async () => {
await PageObjects.wazuhCommon.OpenIntegrityMonitoring();
- es_index = WAZUH_ALERTS_PATTERN;
+ es_index = getSettingDefaultValue('pattern');
});
beforeEach(async () => {
diff --git a/test/functional/apps/overview/_security_events.ts b/test/functional/apps/overview/_security_events.ts
index 50bf599f05..042137b813 100644
--- a/test/functional/apps/overview/_security_events.ts
+++ b/test/functional/apps/overview/_security_events.ts
@@ -13,7 +13,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../../test/functional/ftr_provider_context';
import { SearchParams } from 'elasticsearch';
-import { WAZUH_ALERTS_PATTERN } from '../../../../common/constants';
+import { getSettingDefaultValue } from '../../../../common/services/settings';
export default function({getService, getPageObjects, }: FtrProviderContext) {
const areaChart = getService('areaChart');
@@ -34,7 +34,7 @@ export default function({getService, getPageObjects, }: FtrProviderContext) {
let es_index: string;
before(async () => {
await PageObjects.wazuhCommon.OpenSecurityEvents();
- es_index = WAZUH_ALERTS_PATTERN;
+ es_index = getSettingDefaultValue('pattern');
});
beforeEach(async () => {
diff --git a/test/server/wazuh-elastic.js b/test/server/wazuh-elastic.js
index 2143f211c4..0ebbed2ce4 100644
--- a/test/server/wazuh-elastic.js
+++ b/test/server/wazuh-elastic.js
@@ -1,6 +1,6 @@
const chai = require('chai');
const needle = require('needle');
-const { WAZUH_ALERTS_PATTERN, PLUGIN_PLATFORM_REQUEST_HEADERS } = require('../../common/constants');
+const { PLUGIN_PLATFORM_REQUEST_HEADERS } = require('../../common/constants');
const kibanaServer = process.env.KIBANA_IP || 'localhost';
@@ -15,7 +15,7 @@ describe('wazuh-elastic', () => {
it('GET /elastic/known-fields/{pattern}', async () => {
const res = await needle(
'get',
- `${kibanaServer}:5601/elastic/known-fields/${WAZUH_ALERTS_PATTERN}`,
+ `${kibanaServer}:5601/elastic/known-fields/${getSettingDefaultValue('pattern')}`,
{},
headers
);
@@ -23,7 +23,7 @@ describe('wazuh-elastic', () => {
res.body.output.should.be.a('object');
//res.body.output._index.should.be.eql('.kibana');
res.body.output._type.should.be.eql('doc');
- res.body.output._id.should.be.eql(`index-pattern:${WAZUH_ALERTS_PATTERN}`);
+ res.body.output._id.should.be.eql(`index-pattern:${getSettingDefaultValue('pattern')}`);
});
});
@@ -31,7 +31,7 @@ describe('wazuh-elastic', () => {
it('GET /elastic/visualizations/{tab}/{pattern}', async () => {
const res = await needle(
'get',
- `${kibanaServer}:5601/elastic/visualizations/overview-general/${WAZUH_ALERTS_PATTERN}`,
+ `${kibanaServer}:5601/elastic/visualizations/overview-general/${getSettingDefaultValue('pattern')}`,
{},
headers
);
@@ -46,7 +46,7 @@ describe('wazuh-elastic', () => {
it('POST /elastic/visualizations/{tab}/{pattern}', async () => {
const res = await needle(
'post',
- `${kibanaServer}:5601/elastic/visualizations/cluster-monitoring/${WAZUH_ALERTS_PATTERN}`,
+ `${kibanaServer}:5601/elastic/visualizations/cluster-monitoring/${getSettingDefaultValue('pattern')}`,
{ nodes: { items: [], name: 'node01' } },
headers
);
@@ -63,19 +63,19 @@ describe('wazuh-elastic', () => {
it('GET /elastic/template/{pattern}', async () => {
const res = await needle(
'get',
- `${kibanaServer}:5601/elastic/template/${WAZUH_ALERTS_PATTERN}`,
+ `${kibanaServer}:5601/elastic/template/${getSettingDefaultValue('pattern')}`,
{},
headers
);
res.body.statusCode.should.be.eql(200);
res.body.status.should.be.eql(true);
- res.body.data.should.be.eql(`Template found for ${WAZUH_ALERTS_PATTERN}`);
+ res.body.data.should.be.eql(`Template found for ${getSettingDefaultValue('pattern')}`);
});
it('GET /elastic/index-patterns/{pattern}', async () => {
const res = await needle(
'get',
- `${kibanaServer}:5601/elastic/index-patterns/${WAZUH_ALERTS_PATTERN}`,
+ `${kibanaServer}:5601/elastic/index-patterns/${getSettingDefaultValue('pattern')}`,
{},
headers
);