From 57e345e56abceb2deb2a310aec1b9a61e789918c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Mon, 2 Dec 2024 13:59:00 +0100 Subject: [PATCH] fix(initlization): lint --- .eslintrc.js | 8 + ...e-vulnerabilities-states-index-pattern.tsx | 32 ++- .../react-services/navigation-service.tsx | 93 ++++---- .../server/initialization/index-patterns.ts | 200 ++++++++++-------- .../server/initialization/server-api.test.ts | 14 +- .../server/initialization/server-api.ts | 159 ++++++++------ .../server/initialization/settings.ts | 162 +++++++------- .../server/initialization/templates.ts | 123 ++++++----- plugins/wazuh-core/server/plugin.ts | 83 ++++---- .../services/initialization/initialization.ts | 48 +++-- .../initialization/lib/initialization-task.ts | 47 ++-- .../server/services/initialization/routes.ts | 92 ++++---- .../server/services/initialization/types.ts | 16 +- plugins/wazuh-core/server/types.ts | 6 +- 14 files changed, 620 insertions(+), 463 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3309297533..28118e1374 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { 'react-hooks', '@typescript-eslint', 'unicorn', + 'import', 'prettier', '@stylistic', ], @@ -210,6 +211,12 @@ module.exports = { '@typescript-eslint/naming-convention': [ 'error', { selector: 'default', format: ['camelCase'] }, + { selector: 'import', format: ['camelCase', 'PascalCase'] }, + { + selector: 'variable', + types: ['function'], + format: ['camelCase', 'PascalCase'], + }, { selector: ['objectLiteralProperty', 'typeProperty'], format: null, @@ -225,6 +232,7 @@ module.exports = { { selector: ['variable'], modifiers: ['global'], + types: ['number', 'string'], format: ['UPPER_CASE'], }, { diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx index c5c819eab2..98aa57da9d 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; +import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { withGuardAsync } from '../../../../common/hocs'; import { getSavedObjects } from '../../../../../kibana-services'; import { SavedObject } from '../../../../../react-services'; import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../../../../../common/constants'; -import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; import { vulnerabilityDetection } from '../../../../../utils/applications'; import { LoadingSpinnerDataSource } from '../../../../common/loading/loading-spinner-data-source'; -import NavigationService from '../../../../../react-services/navigation-service'; +import { NavigationService } from '../../../../../react-services/navigation-service'; const INDEX_PATTERN_CREATION_NO_INDEX = 'INDEX_PATTERN_CREATION_NO_INDEX'; @@ -20,8 +20,9 @@ async function checkExistenceIndexPattern(indexPatternID: string) { async function checkExistenceIndices(indexPatternId: string) { try { const fields = await SavedObject.getIndicesFields(indexPatternId); + return { exist: true, fields }; - } catch (error) { + } catch { return { exist: false }; } } @@ -51,7 +52,7 @@ export async function validateVulnerabilitiesStateDataSources({ try { // Check the existence of related index pattern const existIndexPattern = await checkExistenceIndexPattern(indexPatternID); - let indexPattern = existIndexPattern; + const indexPattern = existIndexPattern; // If the idnex pattern does not exist, then check the existence of index if (existIndexPattern?.error?.statusCode === 404) { @@ -70,11 +71,13 @@ export async function validateVulnerabilitiesStateDataSources({ }, }; } + // If the some index match the index pattern, then create the index pattern const resultCreateIndexPattern = await createIndexPattern( indexPatternID, fields, ); + if (resultCreateIndexPattern?.error) { return { ok: true, @@ -86,6 +89,7 @@ export async function validateVulnerabilitiesStateDataSources({ }, }; } + /* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should redirects to the Dashboard tab. We want to redirect to this view, because we need the component is visible (visualizations) to ensure the process that defines the filters for the @@ -95,6 +99,7 @@ export async function validateVulnerabilitiesStateDataSources({ */ NavigationService.getInstance().navigateToApp(vulnerabilityDetection.id); } + return { ok: false, data: { indexPattern }, @@ -127,10 +132,13 @@ const errorPromptBody = { ), }; -export const PromptCheckIndex = props => { +export const PromptCheckIndex = (props: { + error: { title: string; message: string; type?: string }; + refresh: () => void; +}) => { const { refresh } = props; - const { title, message } = props?.error; - const body = errorPromptBody?.[props?.error?.type] ||

{message}

; + const { title, message } = props.error; + const body = errorPromptBody?.[props.error?.type] ||

{message}

; return ( { ); }; -const mapStateToProps = state => ({ - vulnerabilitiesStatesindexPatternID: - state.appConfig.data['vulnerabilities.pattern'], -}); +const mapStateToProps = state => { + return { + vulnerabilitiesStatesindexPatternID: + state.appConfig.data['vulnerabilities.pattern'], + }; +}; export const withVulnerabilitiesStateDataSource = compose( connect(mapStateToProps), diff --git a/plugins/main/public/react-services/navigation-service.tsx b/plugins/main/public/react-services/navigation-service.tsx index 8c0a032e51..5e001a9ecd 100644 --- a/plugins/main/public/react-services/navigation-service.tsx +++ b/plugins/main/public/react-services/navigation-service.tsx @@ -1,13 +1,14 @@ import { Location, Action, History } from 'history'; +import rison from 'rison-node'; import { getCore } from '../kibana-services'; import { NavigateToAppOptions } from '../../../../src/core/public'; -import { getIndexPattern } from './elastic_helpers'; import { buildPhraseFilter } from '../../../../src/plugins/data/common'; -import rison from 'rison-node'; +import { getIndexPattern } from './elastic_helpers'; class NavigationService { + // eslint-disable-next-line no-use-before-define private static instance: NavigationService; - private history: History; + private readonly history: History; private constructor(history: History) { this.history = history; @@ -19,6 +20,7 @@ class NavigationService { } else if (!NavigationService.instance) { throw new Error('NavigationService must be initialized with a history.'); } + return NavigationService.instance; } @@ -56,30 +58,31 @@ class NavigationService { ? this.buildSearch(params) : this.buildSearch(this.getParams()); const locationHash = this.getHash(); + this.navigate( `${newPath}${queryParams ? `?${queryParams}` : ''}${locationHash}`, ); } public navigate(path: string, state?: any): void { - if (!state) { - this.history.push(path); - } else { + if (state) { this.history.push({ pathname: path, state, }); + } else { + this.history.push(path); } } public replace(path: string, state?: any): void { - if (!state) { - this.history.replace(path); - } else { + if (state) { this.history.replace({ pathname: path, state, }); + } else { + this.history.replace(path); } } @@ -96,13 +99,14 @@ class NavigationService { } public reload(): void { - window.location.reload(); + globalThis.location.reload(); } public listen( listener: (location: Location, action: Action) => void, ): () => void { const unlisten = this.history.listen(listener); + return unlisten; } @@ -125,26 +129,27 @@ class NavigationService { } public buildSearch(search: URLSearchParams) { - return Array.from(search.entries()) + return [...search.entries()] .map(([key, value]) => `${key}=${value}`) .join('&'); } - public updateAndNavigateSearchParams(params: { - [key: string]: string | null; - }): void { + public updateAndNavigateSearchParams( + params: Record, + ): void { const urlParams = this.getParams(); // Update or delete parameters according to their value - Object.entries(params).forEach(([key, value]) => { + for (const [key, value] of Object.entries(params)) { if (value === null) { urlParams.delete(key); } else { urlParams.set(key, value); } - }); + } const queryString = this.buildSearch(urlParams); + this.navigate(`${this.getPathname()}?${queryString}`); } @@ -152,16 +157,17 @@ class NavigationService { this.updateAndNavigateSearchParams({ tab: newTab }); } - public switchSubTab = (subTab: string): void => { + public switchSubTab(subTab: string): void { this.updateAndNavigateSearchParams({ tabView: subTab }); - }; + } /* - TODO: Analyze and improve this function taking into account whether buildFilter_w is still used and whether the implementation with respect to the middle button is correct in navigateToModule + TODO: Analyze and improve this function taking into account whether buildFilterW is still used and whether the implementation with respect to the middle button is correct in navigateToModule */ - private buildFilter_w(filters, indexPattern) { + private buildFilterW(filters, indexPattern) { const filtersArray: any[] = []; - Object.keys(filters).forEach(currentFilter => { + + for (const currentFilter of Object.keys(filters)) { filtersArray.push({ ...buildPhraseFilter( { name: currentFilter, type: 'text' }, @@ -170,41 +176,51 @@ class NavigationService { ), $state: { isImplicit: false, store: 'appState' }, }); - }); + } + return rison.encode({ filters: filtersArray }); } - navigateToModule(e: any, section: string, params: any, navigateMethod?: any) { - e.persist(); // needed to access this event asynchronously - if (e.button == 0) { - // left button clicked - if (navigateMethod) { - navigateMethod(); - return; - } + navigateToModule( + event: any, + section: string, + params: any, + navigateMethod?: any, + ) { + event.persist(); // needed to access this event asynchronously + + if ( + event.button === 0 && // left button clicked + navigateMethod + ) { + navigateMethod(); + + return; } + getIndexPattern().then(indexPattern => { const urlParams = {}; - if (Object.keys(params).length) { - Object.keys(params).forEach(key => { + if (Object.keys(params).length > 0) { + for (const key of Object.keys(params)) { if (key === 'filters') { - urlParams['_w'] = this.buildFilter_w(params[key], indexPattern); + urlParams['_w'] = this.buildFilterW(params[key], indexPattern); } else { urlParams[key] = params[key]; } - }); + } } + const url = Object.entries(urlParams) - .map(e => e.join('=')) + .map(urlParam => urlParam.join('=')) .join('&'); - const currentUrl = window.location.href.split('#/')[0]; + const currentUrl = globalThis.location.href.split('#/')[0]; const newUrl = currentUrl + `#/${section}?` + url; - if (e && (e.which == 2 || e.button == 1)) { + if (event && (event.which === 2 || event.button === 1)) { // middlebutton clicked window.open(newUrl, '_blank', 'noreferrer'); - } else if (e.button == 0) { + } else if (event.button === 0) { // left button clicked if (navigateMethod) { navigateMethod(); @@ -217,3 +233,4 @@ class NavigationService { } export default NavigationService; +export { NavigationService }; diff --git a/plugins/wazuh-core/server/initialization/index-patterns.ts b/plugins/wazuh-core/server/initialization/index-patterns.ts index 012f8be2a8..42b4e73f57 100644 --- a/plugins/wazuh-core/server/initialization/index-patterns.ts +++ b/plugins/wazuh-core/server/initialization/index-patterns.ts @@ -4,63 +4,32 @@ import { InitializationTaskRunContext, } from '../services'; -interface ensureIndexPatternExistenceContextTask { +interface EnsureIndexPatternExistenceContextTask { indexPatternID: string; options: any; } -interface ensureIndexPatternExistenceContextTaskWithConfigurationSetting - extends ensureIndexPatternExistenceContextTask { +interface EnsureIndexPatternExistenceContextTaskWithConfigurationSetting + extends EnsureIndexPatternExistenceContextTask { configurationSettingKey: string; } -const decoratorCheckIsEnabled = fn => { - return async ( +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const decoratorCheckIsEnabled = + callback => + async ( ctx: InitializationTaskRunContext, { configurationSettingKey, ...ctxTask - }: ensureIndexPatternExistenceContextTaskWithConfigurationSetting, + }: EnsureIndexPatternExistenceContextTaskWithConfigurationSetting, ) => { if (await ctx.configuration.get(configurationSettingKey)) { - await fn(ctx, ctxTask); + await callback(ctx, ctxTask); } else { ctx.logger.info(`Check [${configurationSettingKey}]: disabled. Skipped.`); } }; -}; - -export const ensureIndexPatternExistence = async ( - { logger, savedObjectsClient, indexPatternsClient }, - { indexPatternID, options = {} }: ensureIndexPatternExistenceContextTask, -) => { - try { - logger.debug( - `Checking existence of index pattern with ID [${indexPatternID}]`, - ); - const response = await savedObjectsClient.get( - 'index-pattern', - indexPatternID, - ); - logger.debug(`Index pattern with ID [${indexPatternID}] exists`); - return response; - } catch (error) { - // Get not found saved object - if (error?.output?.statusCode === 404) { - // Create index pattern - logger.info(`Index pattern with ID [${indexPatternID}] does not exist`); - return await createIndexPattern( - { logger, savedObjectsClient, indexPatternsClient }, - indexPatternID, - options, - ); - } else { - throw new Error( - `index pattern with ID [${indexPatternID}] existence could not be checked due to: ${error.message}`, - ); - } - } -}; async function getFieldMappings( { logger, indexPatternsClient }, @@ -74,11 +43,13 @@ async function getFieldMappings( // meta_fields=_source&meta_fields=_id&meta_fields=_type&meta_fields=_index&meta_fields=_score metaFields: ['_source', '_id', '_type', '_index', '_score'], }); + logger.debug( `Fields for index pattern with title [${indexPatternTitle}]: ${JSON.stringify( fields, )}`, ); + return fields; } @@ -87,30 +58,32 @@ async function createIndexPattern( indexPatternID, options: { fieldsNoIndices?: any; - savedObjectOverwrite?: { [key: string]: any }; + savedObjectOverwrite?: Record; } = {}, ) { try { let fields; + try { fields = await getFieldMappings( { logger, indexPatternsClient }, indexPatternID, ); - } catch (e) { - if (e?.output?.statusCode === 404 && options.fieldsNoIndices) { + } catch (error) { + if (error?.output?.statusCode === 404 && options.fieldsNoIndices) { const message = `Fields for index pattern with ID [${indexPatternID}] could not be obtained. This could indicate there are not matching indices because they were not generated or there is some error in the process that generates and indexes that data. The index pattern will be created with a set of pre-defined fields.`; + logger.warn(message); fields = options.fieldsNoIndices; } else { - throw e; + throw error; } } const savedObjectData = { title: indexPatternID, fields: JSON.stringify(fields), - ...(options?.savedObjectOverwrite || {}), + ...options?.savedObjectOverwrite, }; logger.debug( @@ -126,9 +99,10 @@ async function createIndexPattern( refresh: true, }, ); - const indexPatternCreatedMessage = `Created index pattern with ID [${response.id}] title [${response.attributes.title}]`; + logger.info(indexPatternCreatedMessage); + return response; } catch (error) { throw new Error( @@ -137,21 +111,60 @@ async function createIndexPattern( } } +export const ensureIndexPatternExistence = async ( + { logger, savedObjectsClient, indexPatternsClient }, + { indexPatternID, options = {} }: EnsureIndexPatternExistenceContextTask, +) => { + try { + logger.debug( + `Checking existence of index pattern with ID [${indexPatternID}]`, + ); + + const response = await savedObjectsClient.get( + 'index-pattern', + indexPatternID, + ); + + logger.debug(`Index pattern with ID [${indexPatternID}] exists`); + + return response; + } catch (error) { + // Get not found saved object + if (error?.output?.statusCode === 404) { + // Create index pattern + logger.info(`Index pattern with ID [${indexPatternID}] does not exist`); + + return await createIndexPattern( + { logger, savedObjectsClient, indexPatternsClient }, + indexPatternID, + options, + ); + } else { + throw new Error( + `index pattern with ID [${indexPatternID}] existence could not be checked due to: ${error.message}`, + ); + } + } +}; + function getSavedObjectsClient( ctx: InitializationTaskRunContext, scope: InitializationTaskContext, ) { switch (scope) { - case 'internal': + case 'internal': { return ctx.core.savedObjects.createInternalRepository(); - break; - case 'user': + } + + case 'user': { return ctx.core.savedObjects.savedObjectsStart.getScopedClient( ctx.request, ); + } + + default: { break; - default: - break; + } } } @@ -160,18 +173,21 @@ function getIndexPatternsClient( scope: InitializationTaskContext, ) { switch (scope) { - case 'internal': + case 'internal': { return new IndexPatternsFetcher( ctx.core.opensearch.legacy.client.callAsInternalUser, ); - break; - case 'user': + } + + case 'user': { return new IndexPatternsFetcher( ctx.core.opensearch.legacy.client.callAsCurrentUser, ); + } + + default: { break; - default: - break; + } } } @@ -181,14 +197,17 @@ function getIndexPatternID( rest: any, ) { switch (scope) { - case 'internal': + case 'internal': { return rest.getIndexPatternID(ctx); - break; - case 'user': + } + + case 'user': { return ctx.getIndexPatternID(ctx); + } + + default: { break; - default: - break; + } } } @@ -200,32 +219,37 @@ export const initializationTaskCreatorIndexPattern = ({ }: { getIndexPatternID: (ctx: any) => Promise; taskName: string; + // eslint-disable-next-line @typescript-eslint/no-empty-object-type options: {}; configurationSettingKey: string; -}) => ({ - name: taskName, - async run(ctx: InitializationTaskRunContext) { - let indexPatternID; - try { - ctx.logger.debug('Starting index pattern saved object'); - indexPatternID = await getIndexPatternID(ctx, ctx.scope, rest); - - // Get clients depending on the scope - const savedObjectsClient = getSavedObjectsClient(ctx, ctx.scope); - const indexPatternsClient = getIndexPatternsClient(ctx, ctx.scope); - - return await ensureIndexPatternExistence( - { ...ctx, indexPatternsClient, savedObjectsClient }, - { - indexPatternID, - options, - configurationSettingKey, - }, - ); - } catch (error) { - const message = `Error initilizating index pattern with ID [${indexPatternID}]: ${error.message}`; - ctx.logger.error(message); - throw new Error(message); - } - }, -}); +}) => { + return { + name: taskName, + async run(ctx: InitializationTaskRunContext) { + let indexPatternID; + + try { + ctx.logger.debug('Starting index pattern saved object'); + indexPatternID = await getIndexPatternID(ctx, ctx.scope, rest); + + // Get clients depending on the scope + const savedObjectsClient = getSavedObjectsClient(ctx, ctx.scope); + const indexPatternsClient = getIndexPatternsClient(ctx, ctx.scope); + + return await ensureIndexPatternExistence( + { ...ctx, indexPatternsClient, savedObjectsClient }, + { + indexPatternID, + options, + configurationSettingKey, + }, + ); + } catch (error) { + const message = `Error initilizating index pattern with ID [${indexPatternID}]: ${error.message}`; + + ctx.logger.error(message); + throw new Error(message); + } + }, + }; +}; diff --git a/plugins/wazuh-core/server/initialization/server-api.test.ts b/plugins/wazuh-core/server/initialization/server-api.test.ts index dfe1e20c13..cb1aec5090 100644 --- a/plugins/wazuh-core/server/initialization/server-api.test.ts +++ b/plugins/wazuh-core/server/initialization/server-api.test.ts @@ -45,6 +45,7 @@ describe('ServerAPIConnectionCompatibility', () => { `Check server API connection and compatibility for the server API hosts`, async ({ apiHostID, apiVersionResponse, isCompatible }) => { const loggerMock = jest.fn(); + await ServerAPIConnectionCompatibility( { manageHosts: { @@ -58,11 +59,13 @@ describe('ServerAPIConnectionCompatibility', () => { }, serverAPIClient: { asInternalUser: { - request: () => ({ - data: { - data: apiVersionResponse, - }, - }), + request: () => { + return { + data: { + data: apiVersionResponse, + }, + }; + }, }, }, }, @@ -72,6 +75,7 @@ describe('ServerAPIConnectionCompatibility', () => { expect(loggerMock).toHaveBeenCalledWith( `Checking the connection and compatibility with server API [${apiHostID}]`, ); + if (apiVersionResponse.api_version) { if (isCompatible === true) { expect(loggerMock).toHaveBeenCalledWith( diff --git a/plugins/wazuh-core/server/initialization/server-api.ts b/plugins/wazuh-core/server/initialization/server-api.ts index ab92208203..d6b15b868c 100644 --- a/plugins/wazuh-core/server/initialization/server-api.ts +++ b/plugins/wazuh-core/server/initialization/server-api.ts @@ -6,112 +6,131 @@ import { webDocumentationLink } from '../../common/services/web_documentation'; import { version as appVersion } from '../../package.json'; import { InitializationTaskRunContext } from '../services'; -export const initializationTaskCreatorServerAPIConnectionCompatibility = ({ - taskName, -}: { - taskName: string; -}) => ({ - name: taskName, - async run(ctx: InitializationTaskRunContext) { - try { - ctx.logger.debug( - 'Starting check server API connection and compatibility', - ); - const results = await ServersAPIConnectionCompatibility(ctx); - ctx.logger.info( - 'Start check server API connection and compatibility finished', - ); - return results; - } catch (error) { - const message = `Error checking server API connection and compatibility: ${error.message}`; - ctx.logger.error(message); - throw new Error(message); - } - }, -}); - -async function ServersAPIConnectionCompatibility( - ctx: InitializationTaskRunContext, +export function checkAppServerCompatibility( + appVersion: string, + serverAPIVersion: string, ) { - if (ctx.scope === 'user' && ctx.request?.query?.apiHostID) { - const host = await ctx.manageHosts.get(ctx.request.query.apiHostID, { - excludePassword: true, - }); - - ctx.logger.debug(`APP version [${appVersion}]`); - - return await ServerAPIConnectionCompatibility(ctx, host.id, appVersion); - } else { - const hosts = await ctx.manageHosts.get(undefined, { - excludePassword: true, - }); - - ctx.logger.debug(`APP version [${appVersion}]`); + const api = /v?(?\d+)\.(?\d+)\.(?\d+)/.exec( + serverAPIVersion, + ); + const [appVersionMajor, appVersionMinor] = appVersion.split('.'); - return await Promise.all( - hosts.map(async ({ id: apiHostID }: { id: string }) => - ServerAPIConnectionCompatibility(ctx, apiHostID, appVersion), - ), - ); - } + return ( + api?.groups?.major === appVersionMajor && + api?.groups?.minor === appVersionMinor + ); } -export async function ServerAPIConnectionCompatibility( +export async function serverAPIConnectionCompatibility( ctx: InitializationTaskRunContext, apiHostID: string, appVersion: string, ) { let connection = null, compatibility = null, - api_version = null; + apiVersion = null; + try { ctx.logger.debug( `Checking the connection and compatibility with server API [${apiHostID}]`, ); + const response = await ctx.serverAPIClient.asInternalUser.request( 'GET', '/', {}, { apiHostID }, ); + connection = true; - api_version = response?.data?.data?.api_version; - if (!api_version) { + apiVersion = response?.data?.data?.api_version; + + if (!apiVersion) { throw new Error('version is not found in the response of server API'); } - ctx.logger.debug(`Server API version [${api_version}]`); - if (!checkAppServerCompatibility(appVersion, api_version)) { + + ctx.logger.debug(`Server API version [${apiVersion}]`); + + if (checkAppServerCompatibility(appVersion, apiVersion)) { + compatibility = true; + ctx.logger.info( + `Server API [${apiHostID}] version [${apiVersion}] is compatible with the ${PLUGIN_APP_NAME} version`, + ); + } else { compatibility = false; ctx.logger.warn( - `Server API [${apiHostID}] version [${api_version}] is not compatible with the ${PLUGIN_APP_NAME} version [${appVersion}]. Major and minor number must match at least. It is recommended the server API and ${PLUGIN_APP_NAME} version are equals. Read more about this error in our troubleshooting guide: ${webDocumentationLink( + `Server API [${apiHostID}] version [${apiVersion}] is not compatible with the ${PLUGIN_APP_NAME} version [${appVersion}]. Major and minor number must match at least. It is recommended the server API and ${PLUGIN_APP_NAME} version are equals. Read more about this error in our troubleshooting guide: ${webDocumentationLink( PLUGIN_PLATFORM_WAZUH_DOCUMENTATION_URL_PATH_TROUBLESHOOTING, )}.`, ); - } else { - compatibility = true; - ctx.logger.info( - `Server API [${apiHostID}] version [${api_version}] is compatible with the ${PLUGIN_APP_NAME} version`, - ); } } catch (error) { ctx.logger.warn( `Error checking the connection and compatibility with server API [${apiHostID}]: ${error.message}`, ); } finally { - return { connection, compatibility, api_version, id: apiHostID }; + // eslint-disable-next-line no-unsafe-finally + return { + connection, + compatibility, + api_version: apiVersion, + id: apiHostID, + }; } } -export function checkAppServerCompatibility( - appVersion: string, - serverAPIVersion: string, +async function serversAPIConnectionCompatibility( + ctx: InitializationTaskRunContext, ) { - const api = /v?(?\d+)\.(?\d+)\.(?\d+)/.exec( - serverAPIVersion, - ); - const [appVersionMajor, appVersionMinor] = appVersion.split('.'); - return ( - api?.groups?.major === appVersionMajor && - api?.groups?.minor === appVersionMinor - ); + if (ctx.scope === 'user' && ctx.request?.query?.apiHostID) { + const host = await ctx.manageHosts.get(ctx.request.query.apiHostID, { + excludePassword: true, + }); + + ctx.logger.debug(`APP version [${appVersion}]`); + + return await serverAPIConnectionCompatibility(ctx, host.id, appVersion); + } else { + const hosts = await ctx.manageHosts.get(undefined, { + excludePassword: true, + }); + + ctx.logger.debug(`APP version [${appVersion}]`); + + return await Promise.all( + hosts.map(async ({ id: apiHostID }: { id: string }) => + serverAPIConnectionCompatibility(ctx, apiHostID, appVersion), + ), + ); + } } + +export const initializationTaskCreatorServerAPIConnectionCompatibility = ({ + taskName, +}: { + taskName: string; +}) => { + return { + name: taskName, + async run(ctx: InitializationTaskRunContext) { + try { + ctx.logger.debug( + 'Starting check server API connection and compatibility', + ); + + const results = await serversAPIConnectionCompatibility(ctx); + + ctx.logger.info( + 'Start check server API connection and compatibility finished', + ); + + return results; + } catch (error) { + const message = `Error checking server API connection and compatibility: ${error.message}`; + + ctx.logger.error(message); + throw new Error(message); + } + }, + }; +}; diff --git a/plugins/wazuh-core/server/initialization/settings.ts b/plugins/wazuh-core/server/initialization/settings.ts index 4c92d31663..2391cec165 100644 --- a/plugins/wazuh-core/server/initialization/settings.ts +++ b/plugins/wazuh-core/server/initialization/settings.ts @@ -13,14 +13,15 @@ */ import { isEqual } from 'lodash'; +import { IUiSettingsClient } from 'src/core/server'; import { InitializationTaskContext, InitializationTaskRunContext, } from '../services'; -import { IUiSettingsClient } from 'src/core/server'; -const decoratorCheckIsEnabled = fn => { - return async ( +const decoratorCheckIsEnabled = + callback => + async ( ctx: InitializationTaskRunContext, { configurationSetting, @@ -28,12 +29,41 @@ const decoratorCheckIsEnabled = fn => { }: { key: string; value: any; configurationSetting: string }, ) => { if (await ctx.configuration.get(configurationSetting)) { - await fn(ctx, ctxTask); + await callback(ctx, ctxTask); } else { ctx.logger.info(`Check [${configurationSetting}]: disabled. Skipped.`); } }; -}; + +function stringifySetting(setting: any) { + try { + return JSON.stringify(setting); + } catch { + return setting; + } +} + +async function updateSetting( + uiSettingsClient: IUiSettingsClient, + pluginPlatformSettingName: string, + defaultAppValue: any, + retries = 3, +): Promise { + return await uiSettingsClient + .set(pluginPlatformSettingName, defaultAppValue) + .catch(async error => { + if (retries > 0) { + return await updateSetting( + uiSettingsClient, + pluginPlatformSettingName, + defaultAppValue, + --retries, + ); + } + + throw error; + }); +} export const checkPluginPlatformSettings = decoratorCheckIsEnabled( async ( @@ -47,6 +77,7 @@ export const checkPluginPlatformSettings = decoratorCheckIsEnabled( }: { key: string; value: any }, ) => { logger.debug(`Getting setting [${pluginPlatformSettingName}]...`); + const valuePluginPlatformSetting = await uiSettingsClient.get( pluginPlatformSettingName, ); @@ -54,6 +85,7 @@ export const checkPluginPlatformSettings = decoratorCheckIsEnabled( valuePluginPlatformSetting, defaultAppValue, ); + logger.debug( `Check setting [${pluginPlatformSettingName}]: ${stringifySetting( valuePluginPlatformSetting, @@ -74,6 +106,7 @@ export const checkPluginPlatformSettings = decoratorCheckIsEnabled( valuePluginPlatformSetting ? 'yes' : 'no' }`, ); + if (!valuePluginPlatformSetting || settingsAreDifferent) { logger.debug(`Updating [${pluginPlatformSettingName}] setting...`); await updateSetting( @@ -90,48 +123,24 @@ export const checkPluginPlatformSettings = decoratorCheckIsEnabled( }, ); -async function updateSetting( - uiSettingsClient: IUiSettingsClient, - pluginPlatformSettingName: string, - defaultAppValue: any, - retries: number = 3, -): Promise { - return await uiSettingsClient - .set(pluginPlatformSettingName, defaultAppValue) - .catch(async error => { - if (retries > 0) { - return await updateSetting( - uiSettingsClient, - pluginPlatformSettingName, - defaultAppValue, - --retries, - ); - } - throw error; - }); -} - -function stringifySetting(setting: any) { - try { - return JSON.stringify(setting); - } catch (error) { - return setting; - } -} - function getSavedObjectsClient( ctx: InitializationTaskRunContext, scope: InitializationTaskContext, ) { switch (scope) { - case 'internal': + case 'internal': { return ctx.core.savedObjects.createInternalRepository(); - case 'user': + } + + case 'user': { return ctx.core.savedObjects.savedObjectsStart.getScopedClient( ctx.request, ); - default: + } + + default: { break; + } } } @@ -141,53 +150,58 @@ function getUiSettingsClient( client: any, ) { switch (scope) { - case 'internal': + case 'internal': { return ctx.core.uiSettings.asScopedToClient(client); + } - case 'user': + case 'user': { return ctx.core.uiSettings.uiSettingsStart.asScopedToClient(client); + } - default: + default: { break; + } } } export const initializationTaskCreatorSetting = ( setting: { key: string; value: any; configurationSetting: string }, taskName: string, -) => ({ - name: taskName, - async run(ctx: InitializationTaskRunContext) { - try { - ctx.logger.debug('Starting setting'); - - // Get clients depending on the scope - const savedObjectsClient = getSavedObjectsClient(ctx, ctx.scope); - const uiSettingsClient = getUiSettingsClient( - ctx, - ctx.scope, - savedObjectsClient, - ); +) => { + return { + name: taskName, + async run(ctx: InitializationTaskRunContext) { + try { + ctx.logger.debug('Starting setting'); - const { key, value, configurationSetting } = setting; + // Get clients depending on the scope + const savedObjectsClient = getSavedObjectsClient(ctx, ctx.scope); + const uiSettingsClient = getUiSettingsClient( + ctx, + ctx.scope, + savedObjectsClient, + ); + const { key, value, configurationSetting } = setting; - await checkPluginPlatformSettings( - { - logger: ctx.logger, - uiSettingsClient, - configuration: ctx.configuration, - }, - { - key, - value, - configurationSetting, - }, - ); - ctx.logger.info('Start setting finished'); - } catch (error) { - const message = `Error initilizating setting [${setting.key}]: ${error.message}`; - ctx.logger.error(message); - throw new Error(message); - } - }, -}); + await checkPluginPlatformSettings( + { + logger: ctx.logger, + uiSettingsClient, + configuration: ctx.configuration, + }, + { + key, + value, + configurationSetting, + }, + ); + ctx.logger.info('Start setting finished'); + } catch (error) { + const message = `Error initilizating setting [${setting.key}]: ${error.message}`; + + ctx.logger.error(message); + throw new Error(message); + } + }, + }; +}; diff --git a/plugins/wazuh-core/server/initialization/templates.ts b/plugins/wazuh-core/server/initialization/templates.ts index 085cfb8d2c..2afb6d044f 100644 --- a/plugins/wazuh-core/server/initialization/templates.ts +++ b/plugins/wazuh-core/server/initialization/templates.ts @@ -1,5 +1,43 @@ import { InitializationTaskRunContext } from '../services'; +export function getTemplateForIndexPattern( + indexPatternTitle: string, + templates: { name: string; index_patterns: string }[], +) { + // eslint-disable-next-line @typescript-eslint/naming-convention + return templates.filter(({ index_patterns }: { index_patterns: string }) => { + const [, cleanIndexPatterns] = index_patterns.match(/\[(.+)]/) || [ + null, + null, + ]; + + if (!cleanIndexPatterns) { + return false; + } + + const indexPatterns = cleanIndexPatterns.match(/([^\s,]+)/g); + + if (!indexPatterns) { + return false; + } + + const lastChar = indexPatternTitle.at(-1); + const indexPatternTitleCleaned = + lastChar === '*' ? indexPatternTitle.slice(0, -1) : indexPatternTitle; + + return indexPatterns.some(indexPattern => { + const lastChar = indexPattern.at(-1); + const indexPatternCleaned = + lastChar === '*' ? indexPattern.slice(0, -1) : indexPattern; + + return ( + indexPatternCleaned.includes(indexPatternTitleCleaned) || + indexPatternTitleCleaned.includes(indexPatternCleaned) + ); + }); + }); +} + export const checkIndexPatternHasTemplate = async ( { logger }: InitializationTaskRunContext, { @@ -8,16 +46,19 @@ export const checkIndexPatternHasTemplate = async ( }: { indexPatternTitle: string; opensearchClient: any }, ) => { logger.debug('Getting templates'); + const data = await opensearchClient.cat.templates({ format: 'json' }); logger.debug( 'Checking the index pattern with title [${indexPatternTitle}] has defined some template', ); + const templatesFound = getTemplateForIndexPattern( indexPatternTitle, data.body, ); - if (!templatesFound.length) { + + if (templatesFound.length === 0) { throw new Error( `No template found for index pattern with title [${indexPatternTitle}]`, ); @@ -32,39 +73,6 @@ export const checkIndexPatternHasTemplate = async ( ); }; -export function getTemplateForIndexPattern( - indexPatternTitle: string, - templates: { name: string; index_patterns: string }[], -) { - return templates.filter(({ index_patterns }: { index_patterns: string }) => { - const [, cleanIndexPatterns] = index_patterns.match(/\[(.+)\]/) || [ - null, - null, - ]; - if (!cleanIndexPatterns) { - return false; - } - const indexPatterns = cleanIndexPatterns.match(/([^\s,]+)/g); - - if (!indexPatterns) { - return false; - } - - const lastChar = indexPatternTitle[indexPatternTitle.length - 1]; - const indexPatternTitleCleaned = - lastChar === '*' ? indexPatternTitle.slice(0, -1) : indexPatternTitle; - return indexPatterns.some(indexPattern => { - const lastChar = indexPattern[indexPattern.length - 1]; - const indexPatternCleaned = - lastChar === '*' ? indexPattern.slice(0, -1) : indexPattern; - return ( - indexPatternCleaned.includes(indexPatternTitleCleaned) || - indexPatternTitleCleaned.includes(indexPatternCleaned) - ); - }); - }); -} - export const initializationTaskCreatorExistTemplate = ({ getOpenSearchClient, getIndexPatternTitle, @@ -73,24 +81,29 @@ export const initializationTaskCreatorExistTemplate = ({ getOpenSearchClient: (ctx: InitializationTaskRunContext) => any; getIndexPatternTitle: (ctx: InitializationTaskRunContext) => Promise; taskName: string; -}) => ({ - name: taskName, - async run(ctx: InitializationTaskRunContext) { - let indexPatternTitle; - try { - ctx.logger.debug('Starting check of existent template'); - - const opensearchClient = getOpenSearchClient(ctx); - indexPatternTitle = await getIndexPatternTitle(ctx); - await checkIndexPatternHasTemplate(ctx, { - opensearchClient, - indexPatternTitle, - }); - ctx.logger.info('Start check of existent template finished'); - } catch (error) { - const message = `Error checking of existent template for index pattern with title [${indexPatternTitle}]: ${error.message}`; - ctx.logger.error(message); - throw new Error(message); - } - }, -}); +}) => { + return { + name: taskName, + async run(ctx: InitializationTaskRunContext) { + let indexPatternTitle; + + try { + ctx.logger.debug('Starting check of existent template'); + + const opensearchClient = getOpenSearchClient(ctx); + + indexPatternTitle = await getIndexPatternTitle(ctx); + await checkIndexPatternHasTemplate(ctx, { + opensearchClient, + indexPatternTitle, + }); + ctx.logger.info('Start check of existent template finished'); + } catch (error) { + const message = `Error checking of existent template for index pattern with title [${indexPatternTitle}]: ${error.message}`; + + ctx.logger.error(message); + throw new Error(message); + } + }, + }; +}; diff --git a/plugins/wazuh-core/server/plugin.ts b/plugins/wazuh-core/server/plugin.ts index afb08f0f1a..ea614c532b 100644 --- a/plugins/wazuh-core/server/plugin.ts +++ b/plugins/wazuh-core/server/plugin.ts @@ -6,19 +6,6 @@ import { Logger, } from 'opensearch-dashboards/server'; import { validate as validateNodeCronInterval } from 'node-cron'; -import { - PluginSetup, - WazuhCorePluginSetup, - WazuhCorePluginStart, -} from './types'; -import { setCore } from './plugin-services'; -import { - ManageHosts, - createDashboardSecurity, - ServerAPIClient, - ConfigurationStore, - InitializationService, -} from './services'; import { Configuration } from '../common/services/configuration'; import { PLUGIN_PLATFORM_SETTING_NAME_MAX_BUCKETS, @@ -32,6 +19,19 @@ import { WAZUH_PLUGIN_PLATFORM_SETTING_METAFIELDS, WAZUH_PLUGIN_PLATFORM_SETTING_TIME_FILTER, } from '../common/constants'; +import { + PluginSetup, + WazuhCorePluginSetup, + WazuhCorePluginStart, +} from './types'; +import { setCore } from './plugin-services'; +import { + ManageHosts, + createDashboardSecurity, + ServerAPIClient, + ConfigurationStore, + InitializationService, +} from './services'; import { enhanceConfiguration } from './services/enhance-configuration'; import { initializationTaskCreatorServerAPIConnectionCompatibility } from './initialization/server-api'; import { @@ -39,19 +39,20 @@ import { initializationTaskCreatorIndexPattern, initializationTaskCreatorSetting, } from './initialization'; -import AlertsIndexPatternDefaultFields from './initialization/index-patterns-fields/alerts-fields.json'; -import MonitoringIndexPatternDefaultFields from './initialization/index-patterns-fields/monitoring-fields.json'; -import StatisticsIndexPatternDefaultFields from './initialization/index-patterns-fields/statistics-fields.json'; -import VulnerabilitiesStatesFields from './initialization/index-patterns-fields/vulnerabibility-states-fields.json'; +import alertsIndexPatternDefaultFields from './initialization/index-patterns-fields/alerts-fields.json'; +import monitoringIndexPatternDefaultFields from './initialization/index-patterns-fields/monitoring-fields.json'; +import statisticsIndexPatternDefaultFields from './initialization/index-patterns-fields/statistics-fields.json'; +import vulnerabilitiesStatesFields from './initialization/index-patterns-fields/vulnerabibility-states-fields.json'; export class WazuhCorePlugin implements Plugin { private readonly logger: Logger; - private services: { [key: string]: any }; - private _internal: { [key: string]: any }; + private readonly services: Record; + // eslint-disable-next-line @typescript-eslint/naming-convention + private readonly _internal: Record; - constructor(private initializerContext: PluginInitializerContext) { + constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); this.services = {}; this._internal = {}; @@ -81,14 +82,14 @@ export class WazuhCorePlugin enhanceConfiguration(this.services.configuration); // Register the plugin settings - Object.entries(PLUGIN_SETTINGS).forEach(([key, value]) => - this.services.configuration.register(key, value), - ); + for (const [key, value] of Object.entries(PLUGIN_SETTINGS)) { + this.services.configuration.register(key, value); + } // Add categories to the configuration - Object.entries(PLUGIN_SETTINGS_CATEGORIES).forEach(([key, value]) => { + for (const [key, value] of Object.entries(PLUGIN_SETTINGS_CATEGORIES)) { this.services.configuration.registerCategory({ ...value, id: key }); - }); + } /* Workaround: Redefine the validation functions of cron.statistics.interval setting. Because the settings are defined in the backend and frontend side using the same definitions, @@ -97,16 +98,20 @@ export class WazuhCorePlugin const setting = this.services.configuration._settings.get( 'cron.statistics.interval', ); - !setting.validateUIForm && - (setting.validateUIForm = function (value) { + + if (!setting.validateUIForm) { + setting.validateUIForm = function (value) { return this.validate(value); - }); - !setting.validate && - (setting.validate = function (value: string) { + }; + } + + if (!setting.validate) { + setting.validate = function (value: string) { return validateNodeCronInterval(value) ? undefined : 'Interval is not valid.'; - }); + }; + } this.services.configuration.setup(); @@ -147,7 +152,7 @@ export class WazuhCorePlugin savedObjectOverwrite: { timeFieldName: 'timestamp', }, - fieldsNoIndices: AlertsIndexPatternDefaultFields, + fieldsNoIndices: alertsIndexPatternDefaultFields, }, configurationSettingKey: 'checks.pattern', }), @@ -163,7 +168,7 @@ export class WazuhCorePlugin savedObjectOverwrite: { timeFieldName: 'timestamp', }, - fieldsNoIndices: MonitoringIndexPatternDefaultFields, + fieldsNoIndices: monitoringIndexPatternDefaultFields, }, configurationSettingKey: 'checks.monitoring', // TODO: create new setting }), @@ -176,7 +181,7 @@ export class WazuhCorePlugin ctx.configuration.get('vulnerabilities.pattern'), taskName: 'index-pattern:vulnerabilities-states', options: { - fieldsNoIndices: VulnerabilitiesStatesFields, + fieldsNoIndices: vulnerabilitiesStatesFields, }, configurationSettingKey: 'checks.vulnerability', // TODO: create new setting }), @@ -191,10 +196,10 @@ export class WazuhCorePlugin 'cron.prefix', 'cron.statistics.index.name', ); - const prefixTemplateName = appConfig['cron.prefix']; const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name']; + return `${prefixTemplateName}-${statisticsIndicesTemplateName}-*`; }, taskName: 'index-pattern:statistics', @@ -202,7 +207,7 @@ export class WazuhCorePlugin savedObjectOverwrite: { timeFieldName: 'timestamp', }, - fieldsNoIndices: StatisticsIndexPatternDefaultFields, + fieldsNoIndices: statisticsIndexPatternDefaultFields, }, configurationSettingKey: 'checks.statistics', // TODO: create new setting }), @@ -210,7 +215,7 @@ export class WazuhCorePlugin // Settings // TODO: this task should be registered by the related plugin - [ + for (const setting of [ { key: PLUGIN_PLATFORM_SETTING_NAME_MAX_BUCKETS, value: WAZUH_PLUGIN_PLATFORM_SETTING_MAX_BUCKETS, @@ -226,11 +231,11 @@ export class WazuhCorePlugin value: JSON.stringify(WAZUH_PLUGIN_PLATFORM_SETTING_TIME_FILTER), configurationSetting: 'checks.timeFilter', }, - ].forEach(setting => { + ]) { this.services.initialization.register( initializationTaskCreatorSetting(setting, `setting:${setting.key}`), ); - }); + } // Index pattern templates // Index pattern template: alerts diff --git a/plugins/wazuh-core/server/services/initialization/initialization.ts b/plugins/wazuh-core/server/services/initialization/initialization.ts index 636ef63192..69f05e6336 100644 --- a/plugins/wazuh-core/server/services/initialization/initialization.ts +++ b/plugins/wazuh-core/server/services/initialization/initialization.ts @@ -1,82 +1,104 @@ import { Logger } from 'opensearch-dashboards/server'; +import { INITIALIZATION_TASK } from '../../../common/services/initialization/constants'; import { InitializationTaskDefinition, IInitializationService, InitializationTaskContext, } from './types'; import { addRoutes } from './routes'; -import { INITIALIZATION_TASK } from '../../../common/services/initialization/constants'; import { InitializationTask } from './lib/initialization-task'; export class InitializationService implements IInitializationService { - private items: Map; - private _coreStart: any; - constructor(private logger: Logger, private services: any) { + private readonly items: Map; + private coreStart: any; + + constructor( + private readonly logger: Logger, + private readonly services: any, + ) { this.items = new Map(); } + async setup({ core }) { this.logger.debug('Setup starts'); this.logger.debug('Adding routes'); + const router = core.http.createRouter(); + addRoutes(router, { initialization: this }); this.logger.debug('Added routes'); this.logger.debug('Setup finished'); } + async start({ core }) { this.logger.debug('Start starts'); - this._coreStart = core; + this.coreStart = core; await this.runAsInternal(); this.logger.debug('Start finished'); } + async stop() { this.logger.debug('Stop starts'); this.logger.debug('Stop finished'); } + register(task: InitializationTaskDefinition) { this.logger.debug(`Registering ${task.name}`); + if (this.items.has(task.name)) { throw new Error( `[${task.name}] was already registered. Ensure the name is unique or remove the duplicated registration of same task.`, ); } + this.items.set(task.name, new InitializationTask(task)); this.logger.debug(`Registered ${task.name}`); } + get(name: string) { this.logger.debug(`Getting task: [${name}]`); + if (!this.items.has(name)) { throw new Error(`Task [${name}] not found`); } + return this.items.get(name); } + getAll() { this.logger.debug('Getting all tasks'); - return Array.from(this.items.values()); + + return [...this.items.values()]; } + createRunContext(scope: InitializationTaskContext, context: any = {}) { return { ...this.services, ...context, scope }; } + async runAsInternal(taskNames?: string[]) { const ctx = this.createRunContext(INITIALIZATION_TASK.CONTEXT.INTERNAL, { - core: this._coreStart, + core: this.coreStart, }); + return await this.run(ctx, taskNames); } + createNewTaskFromRegisteredTask(name: string) { const task = this.get(name) as InitializationTask; + if (!task) { throw new Error(`Task [${name}] is not registered`); } + return new InitializationTask({ name, run: task._run }); } + private async run(ctx, taskNames?: string[]) { try { - if (this.items.size) { - const allTasks = Array.from(this.items.values()); + if (this.items.size > 0) { + const allTasks = [...this.items.values()]; const tasks = taskNames - ? allTasks.filter(({ name }) => - taskNames.some(taskName => taskName === name), - ) + ? allTasks.filter(({ name }) => taskNames.includes(name)) : allTasks; const results = await Promise.all( tasks.map(async item => { @@ -92,10 +114,12 @@ export class InitializationService implements IInitializationService { logger.error( `Error running task [${item.name}]: ${error.message}`, ); + return item.getInfo(); } }), ); + return results; } else { this.logger.info('No tasks'); diff --git a/plugins/wazuh-core/server/services/initialization/lib/initialization-task.ts b/plugins/wazuh-core/server/services/initialization/lib/initialization-task.ts index a6c00c71fd..3d82b91c71 100644 --- a/plugins/wazuh-core/server/services/initialization/lib/initialization-task.ts +++ b/plugins/wazuh-core/server/services/initialization/lib/initialization-task.ts @@ -7,7 +7,8 @@ import { INITIALIZATION_TASK } from '../../../../common/services/initialization/ export class InitializationTask implements IInitializationTask { public name: string; - private _run: any; + // eslint-disable-next-line @typescript-eslint/naming-convention + private readonly _run: any; public status: InitializationTaskRunData['status'] = INITIALIZATION_TASK.RUN_STATUS.NOT_STARTED; public result: InitializationTaskRunData['result'] = @@ -19,10 +20,12 @@ export class InitializationTask implements IInitializationTask { public finishedAt: InitializationTaskRunData['finishedAt'] = null; public duration: InitializationTaskRunData['duration'] = null; public error = null; + constructor(task: InitializationTaskDefinition) { this.name = task.name; this._run = task.run; } + private init() { this.status = INITIALIZATION_TASK.RUN_STATUS.RUNNING; this.result = null; @@ -32,49 +35,53 @@ export class InitializationTask implements IInitializationTask { this.duration = null; this.error = null; } + async run(...params) { if (this.status === INITIALIZATION_TASK.RUN_STATUS.RUNNING) { throw new Error(`Another instance of task ${this.name} is running`); } + let error; + try { this.init(); this.data = await this._run(...params); this.result = INITIALIZATION_TASK.RUN_RESULT.SUCCESS; - } catch (e) { - error = e; + } catch (error_) { + error = error_; this.result = INITIALIZATION_TASK.RUN_RESULT.FAIL; - this.error = e.message; + this.error = error_.message; } finally { this.status = INITIALIZATION_TASK.RUN_STATUS.FINISHED; this.finishedAt = new Date().toISOString(); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const dateStartedAt = new Date(this.startedAt!); const dateFinishedAt = new Date(this.finishedAt); + this.duration = ((dateFinishedAt - dateStartedAt) as number) / 1000; } + if (error) { throw error; } + return this.getInfo(); } getInfo() { - return [ - 'name', - 'status', - 'result', - 'data', - 'createdAt', - 'startedAt', - 'finishedAt', - 'duration', - 'error', - ].reduce( - (accum, item) => ({ - ...accum, - [item]: this[item], - }), - {}, + return Object.fromEntries( + [ + 'name', + 'status', + 'result', + 'data', + 'createdAt', + 'startedAt', + 'finishedAt', + 'duration', + 'error', + ].map(item => [item, this[item]]), ) as IInitializationTask; } } diff --git a/plugins/wazuh-core/server/services/initialization/routes.ts b/plugins/wazuh-core/server/services/initialization/routes.ts index 568e0353a1..2be1b8905e 100644 --- a/plugins/wazuh-core/server/services/initialization/routes.ts +++ b/plugins/wazuh-core/server/services/initialization/routes.ts @@ -1,8 +1,32 @@ import { schema } from '@osd/config-schema'; -export function addRoutes(router, { initialization }) { - const getTaskList = (tasksAsString: string) => tasksAsString.split(','); +const getTaskList = (tasksAsString: string) => tasksAsString.split(','); + +interface EnhancedLoggerLog { + timestamp: string; + level: string; + message: string; +} + +function enhanceTaskLogger(logger) { + const logs: EnhancedLoggerLog[] = []; + const enhancedLogger = { + getLogs() { + return logs; + }, + }; + + for (const level of ['debug', 'info', 'warn', 'error']) { + enhancedLogger[level] = (message: string) => { + logs.push({ timestamp: new Date().toISOString(), level, message }); + logger[level](message); + }; + } + + return enhancedLogger; +} +export function addRoutes(router, { initialization }) { const validateTaskList = schema.maybe( schema.string({ validate(value: string) { @@ -11,14 +35,15 @@ export function addRoutes(router, { initialization }) { const invalidTasks = requestTasks.filter(requestTask => tasks.every(({ name }) => requestTask !== name), ); - if (invalidTasks.length) { + + if (invalidTasks.length > 0) { return `Invalid tasks: ${invalidTasks.join(', ')}`; } - return undefined; + + return; }, }), ); - const apiEndpointBase = '/api/initialization'; // Get the status of internal initialization tasks @@ -37,13 +62,14 @@ export function addRoutes(router, { initialization }) { ? getTaskList(request.query.tasks) : undefined; const logger = context.wazuh_core.logger; + logger.debug(`Getting initialization tasks related to internal scope`); + const tasks = tasksNames ? tasksNames.map(taskName => context.wazuh_core.initialization.get(taskName), ) : context.wazuh_core.initialization.getAll(); - const tasksData = tasks.map(task => task.getInfo()); logger.debug( @@ -60,10 +86,10 @@ export function addRoutes(router, { initialization }) { tasks: tasksData, }, }); - } catch (e) { + } catch (error) { return response.internalError({ body: { - message: `Error getting the internal initialization tasks: ${e.message}`, + message: `Error getting the internal initialization tasks: ${error.message}`, }, }); } @@ -89,9 +115,10 @@ export function addRoutes(router, { initialization }) { const logger = context.wazuh_core.logger; logger.debug(`Running initialization tasks related to internal scope`); - const results = await context.wazuh_core.initialization.runAsInternal( - tasksNames, - ); + + const results = + await context.wazuh_core.initialization.runAsInternal(tasksNames); + logger.info( `Initialization tasks related to internal scope were executed`, ); @@ -104,10 +131,10 @@ export function addRoutes(router, { initialization }) { tasks: results, }, }); - } catch (e) { + } catch (error) { return response.internalError({ body: { - message: `Error running the internal initialization tasks: ${e.message}`, + message: `Error running the internal initialization tasks: ${error.message}`, }, }); } @@ -132,11 +159,12 @@ export function addRoutes(router, { initialization }) { const logger = context.wazuh_core.logger; const username = ''; // TODO: get value const scope = 'user'; + logger.debug( `Getting initialization tasks related to user [${username}] scope [${scope}]`, ); - const initializationTasks = context.wazuh_core.initialization.get(); + const initializationTasks = context.wazuh_core.initialization.get(); const indexPatternTasks = initializationTasks .filter(({ name }) => name.startsWith('index-pattern:')) .map(({ name }) => @@ -151,12 +179,9 @@ export function addRoutes(router, { initialization }) { name, ), ); - const allUserTasks = [...indexPatternTasks, ...settingsTasks]; const tasks = tasksNames - ? allUserTasks.filter(({ name }) => - tasksNames.some(taskName => taskName === name), - ) + ? allUserTasks.filter(({ name }) => tasksNames.includes(name)) : allUserTasks; logger.debug( @@ -171,12 +196,13 @@ export function addRoutes(router, { initialization }) { ); logger.debug(`Running tasks for user [${username}] scope [${scope}]`); + const results = await Promise.all( tasks.map(async task => { const taskLogger = enhanceTaskLogger(logger); - let data; + try { - data = await task.run({ + await task.run({ ...taskContext, // TODO: use user selection index patterns logger: taskLogger, @@ -187,8 +213,10 @@ export function addRoutes(router, { initialization }) { } : {}), }); - } catch (e) { + } catch { + /* empty */ } finally { + // eslint-disable-next-line no-unsafe-finally return { logs: taskLogger.getLogs(), ...task.getInfo(), @@ -201,12 +229,11 @@ export function addRoutes(router, { initialization }) { const initialMessage = 'All the initialization tasks related to user scope were executed.'; - const message = [ initialMessage, results.some(({ error }) => error) && 'There was some errors.', ] - .filter(v => v) + .filter(Boolean) .join(' '); return response.ok({ @@ -215,28 +242,13 @@ export function addRoutes(router, { initialization }) { tasks: results, }, }); - } catch (e) { + } catch (error) { return response.internalError({ body: { - message: `Error initializating the tasks: ${e.message}`, + message: `Error initializating the tasks: ${error.message}`, }, }); } }, ); } - -function enhanceTaskLogger(logger) { - const logs = []; - - return ['debug', 'info', 'warn', 'error'].reduce( - (accum, level) => ({ - ...accum, - [level]: message => { - logs.push({ timestamp: new Date().toISOString(), level, message }); - logger[level].message; - }, - }), - { getLogs: () => logs }, - ); -} diff --git a/plugins/wazuh-core/server/services/initialization/types.ts b/plugins/wazuh-core/server/services/initialization/types.ts index 00eea6bb2b..254dae3af9 100644 --- a/plugins/wazuh-core/server/services/initialization/types.ts +++ b/plugins/wazuh-core/server/services/initialization/types.ts @@ -23,23 +23,23 @@ export interface InitializationTaskRunData { } export interface IInitializationTask extends InitializationTaskRunData { - run(ctx: Context): Promise; - getInfo(): InitializationTaskRunData; + run: (ctx: Context) => Promise; + getInfo: () => InitializationTaskRunData; } export type InitializationTaskContext = 'internal' | 'user'; export interface IInitializationService extends LifecycleService { - register(task: InitializationTaskDefinition): void; - get(taskName: string): InitializationTaskRunData; - getAll(): InitializationTaskRunData[]; - createRunContext( + register: (task: InitializationTaskDefinition) => void; + get: (taskName: string) => InitializationTaskRunData; + getAll: () => InitializationTaskRunData[]; + createRunContext: ( scope: InitializationTaskContext, context: ContextType, - ): { + ) => { scope: InitializationTaskContext; }; - runAsInternal(tasks?: string[]): Promise; + runAsInternal: (tasks?: string[]) => Promise; } export interface InitializationTaskRunContext extends WazuhCoreServices { diff --git a/plugins/wazuh-core/server/types.ts b/plugins/wazuh-core/server/types.ts index 998fd6f26a..d2e7bf16fe 100644 --- a/plugins/wazuh-core/server/types.ts +++ b/plugins/wazuh-core/server/types.ts @@ -23,8 +23,8 @@ export interface WazuhCorePluginStart extends WazuhCoreServices { }; } -export type PluginSetup = { - securityDashboards?: {}; // TODO: Add OpenSearch Dashboards Security interface -}; +export interface PluginSetup { + securityDashboards?: object; // TODO: Add OpenSearch Dashboards Security interface +} export * from './services/initialization/types';