diff --git a/public/controllers/agents.js b/public/controllers/agents.js index b2025c310b..52a8e891ee 100644 --- a/public/controllers/agents.js +++ b/public/controllers/agents.js @@ -331,7 +331,17 @@ function ( $scope.$broadcast('wazuhSearch',{term}) } - $scope.startVis2Png = () => reportingService.startVis2Png($scope.tab, $scope.agent && $scope.agent.id ? $scope.agent.id : true); + $scope.startVis2Png = () => { + const syscollectorFilters = []; + if($scope.tab === 'syscollector' && $scope.agent && $scope.agent.id){ + syscollectorFilters.push(filterHandler.managerQuery( + appState.getClusterInfo().cluster, + true + )); + syscollectorFilters.push(filterHandler.agentQuery($scope.agent.id)); + } + reportingService.startVis2Png($scope.tab, $scope.agent && $scope.agent.id ? $scope.agent.id : true, syscollectorFilters.length ? syscollectorFilters : null); + } //Load try { diff --git a/public/factories/vis-handlers.js b/public/factories/vis-handlers.js index 823fe8b6bc..e0b7ed4c64 100644 --- a/public/factories/vis-handlers.js +++ b/public/factories/vis-handlers.js @@ -25,9 +25,22 @@ app.factory('visHandlers', function() { return list; }; - const getAppliedFilters = () => { - let appliedFilters = {}; + const getAppliedFilters = syscollector => { + const appliedFilters = {}; + if(syscollector){ + Object.assign(appliedFilters, { + filters: syscollector, + time:{ + from: 'now-1d/d', + to: 'now' + }, + searchBar: false, + tables:[] + }); + return appliedFilters; + } + // Check raw response from all rendered tables const tables = list.filter(item => item._scope && item._scope.savedObj && @@ -65,11 +78,11 @@ app.factory('visHandlers', function() { if(list && list.length) { // Parse applied filters for the first visualization const filters = list[0]._scope.savedObj.vis.API.queryFilter.getFilters(); - + // Parse current time range const { from, to } = list[0]._scope.savedObj.vis.API.timeFilter.time; - appliedFilters = { + Object.assign(appliedFilters, { filters, time:{ from: dateMath.parse(from), @@ -79,8 +92,9 @@ app.factory('visHandlers', function() { list[0]._scope.appState.query.query : false, tables - }; + }); } + return appliedFilters; }; diff --git a/public/img/logo.png b/public/img/logo.png index 67cdcade44..22b7a9ca4c 100644 Binary files a/public/img/logo.png and b/public/img/logo.png differ diff --git a/public/services/generic-request.js b/public/services/generic-request.js index 2c3d595f1b..b6faadb1f4 100644 --- a/public/services/generic-request.js +++ b/public/services/generic-request.js @@ -25,7 +25,9 @@ app.service('genericReq', function ($q, $http, appState, wazuhConfig) { const { timeout } = wazuhConfig.getConfig(); const requestHeaders = { headers: { "Content-Type": 'application/json' }, timeout: timeout || 8000 }; const tmpUrl = chrome.addBasePath(path); - + + requestHeaders.headers.pattern = appState.getCurrentPattern(); + try { requestHeaders.headers.id = JSON.parse(appState.getCurrentAPI()).id; } catch (error) { diff --git a/public/services/reporting.js b/public/services/reporting.js index 6dab99ae99..3ca04e7920 100644 --- a/public/services/reporting.js +++ b/public/services/reporting.js @@ -15,7 +15,7 @@ import $ from 'jquery'; uiModules.get('app/wazuh', []) .service('reportingService', function ($rootScope, vis2png, rawVisualizations, visHandlers, genericReq, errorHandler) { return { - startVis2Png: async (tab,isAgents = false) => { + startVis2Png: async (tab, isAgents = false, syscollectorFilters = null) => { try { if(vis2png.isWorking()){ errorHandler.handle('Report in progress', 'Reporting',true); @@ -34,8 +34,8 @@ uiModules.get('app/wazuh', []) vis2png.assignHTMLItem(item,tmpHTMLElement); } - const appliedFilters = visHandlers.getAppliedFilters(); - + const appliedFilters = visHandlers.getAppliedFilters(syscollectorFilters); + const array = await vis2png.checkArray(idArray); const name = `wazuh-${isAgents ? 'agents' : 'overview'}-${tab}-${Date.now() / 1000 | 0}.pdf` diff --git a/public/templates/agents/agents.head b/public/templates/agents/agents.head index c4ad5ea0cd..7641ae8c58 100644 --- a/public/templates/agents/agents.head +++ b/public/templates/agents/agents.head @@ -38,9 +38,9 @@ -
+
- +
diff --git a/public/utils/opensans/Montserrat-Light.ttf b/public/utils/opensans/Montserrat-Light.ttf new file mode 100644 index 0000000000..a3cf5f5832 Binary files /dev/null and b/public/utils/opensans/Montserrat-Light.ttf differ diff --git a/public/utils/opensans/OpenSans-Bold.ttf b/public/utils/opensans/OpenSans-Bold.ttf new file mode 100644 index 0000000000..7b52945603 Binary files /dev/null and b/public/utils/opensans/OpenSans-Bold.ttf differ diff --git a/public/utils/opensans/OpenSans-BoldItalic.ttf b/public/utils/opensans/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000000..a670e14265 Binary files /dev/null and b/public/utils/opensans/OpenSans-BoldItalic.ttf differ diff --git a/public/utils/opensans/OpenSans-Italic.ttf b/public/utils/opensans/OpenSans-Italic.ttf new file mode 100644 index 0000000000..e6c5414173 Binary files /dev/null and b/public/utils/opensans/OpenSans-Italic.ttf differ diff --git a/public/utils/opensans/OpenSans-Light.ttf b/public/utils/opensans/OpenSans-Light.ttf new file mode 100644 index 0000000000..563872c768 Binary files /dev/null and b/public/utils/opensans/OpenSans-Light.ttf differ diff --git a/public/utils/roboto/Roboto-Bold.ttf b/public/utils/roboto/Roboto-Bold.ttf deleted file mode 100644 index aaf374d2cc..0000000000 Binary files a/public/utils/roboto/Roboto-Bold.ttf and /dev/null differ diff --git a/public/utils/roboto/Roboto-BoldItalic.ttf b/public/utils/roboto/Roboto-BoldItalic.ttf deleted file mode 100644 index dcd0f80073..0000000000 Binary files a/public/utils/roboto/Roboto-BoldItalic.ttf and /dev/null differ diff --git a/public/utils/roboto/Roboto-Italic.ttf b/public/utils/roboto/Roboto-Italic.ttf deleted file mode 100644 index f382c68743..0000000000 Binary files a/public/utils/roboto/Roboto-Italic.ttf and /dev/null differ diff --git a/public/utils/roboto/Roboto-Regular.ttf b/public/utils/roboto/Roboto-Regular.ttf deleted file mode 100644 index 3e6e2e7613..0000000000 Binary files a/public/utils/roboto/Roboto-Regular.ttf and /dev/null differ diff --git a/server/controllers/wazuh-api.js b/server/controllers/wazuh-api.js index e49a40da95..a87877d07b 100644 --- a/server/controllers/wazuh-api.js +++ b/server/controllers/wazuh-api.js @@ -425,6 +425,47 @@ export default class WazuhApi { } } + async makeGenericRequest (method, path, data, id) { + try { + const wapi_config = await this.wzWrapper.getWazuhConfigurationById(id); + + if (wapi_config.error_code > 1) { + //Can not connect to elasticsearch + throw new Error('Could not connect with elasticsearch'); + } else if (wapi_config.error_code > 0) { + //Credentials not found + throw new Error('Credentials does not exists'); + } + + if (!data) { + data = {}; + } + + const options = { + headers: { + 'wazuh-app-version': packageInfo.version + }, + username : wapi_config.user, + password : wapi_config.password, + rejectUnauthorized: !wapi_config.insecure + }; + + const fullUrl = getPath(wapi_config) + path; + const response = await needle(method, fullUrl, data, options); + + if(response && response.body && !response.body.error && response.body.data) { + return response.body; + } + + throw response && response.body && response.body.error && response.body.message ? + new Error(response.body.message) : + new Error('Unexpected error fetching data from the Wazuh API'); + + } catch (error) { + return Promise.reject(error); + } + } + requestApi (req, reply) { if (!req.payload.method) { return ErrorResponse('Missing param: method', 3015, 400, reply); diff --git a/server/controllers/wazuh-reporting.js b/server/controllers/wazuh-reporting.js index 327a492558..26e705e810 100644 --- a/server/controllers/wazuh-reporting.js +++ b/server/controllers/wazuh-reporting.js @@ -9,181 +9,304 @@ * * Find more information about this on the LICENSE file. */ -import path from 'path'; -import fs from 'fs'; -import descriptions from '../reporting/tab-description'; -import * as TimSort from 'timsort'; -import rawParser from '../reporting/raw-parser'; -import PdfPrinter from 'pdfmake/src/printer'; +import path from 'path'; +import fs from 'fs'; +import descriptions from '../reporting/tab-description'; +import * as TimSort from 'timsort'; +import rawParser from '../reporting/raw-parser'; +import PdfPrinter from 'pdfmake/src/printer'; import ErrorResponse from './error-response'; - +import VulnerabilityRequest from '../reporting/vulnerability-request'; +import OverviewRequest from '../reporting/overview-request'; +import RootcheckRequest from '../reporting/rootcheck-request'; +import PciRequest from '../reporting/pci-request'; +import GdprRequest from '../reporting/gdpr-request'; +import AuditRequest from '../reporting/audit-request'; +import SyscheckRequest from '../reporting/syscheck-request'; +import PCI from '../integration-files/pci-requirements-pdfmake'; +import GDPR from '../integration-files/gdpr-requirements-pdfmake'; +import PdfTable from '../reporting/generic-table'; +import WazuhApi from './wazuh-api'; +import clockIconRaw from '../reporting/clock-icon-raw'; +import filterIconRaw from '../reporting/filter-icon-raw'; import { AgentsVisualizations, OverviewVisualizations } from '../integration-files/visualizations'; +const REPORTING_PATH = '../../../../optimize/wazuh-reporting'; + export default class WazuhReportingCtrl { - constructor(){ + constructor(server) { + this.server = server; this.fonts = { Roboto: { - normal: path.join(__dirname, '../../public/utils/roboto/Roboto-Regular.ttf'), - bold: path.join(__dirname, '../../public/utils/roboto/Roboto-Bold.ttf'), - italics: path.join(__dirname, '../../public/utils/roboto/Roboto-Italic.ttf'), - bolditalics: path.join(__dirname, '../../public/utils/roboto/Roboto-BoldItalic.ttf') + normal: path.join(__dirname, '../../public/utils/opensans/OpenSans-Light.ttf'), + bold: path.join(__dirname, '../../public/utils/opensans/OpenSans-Bold.ttf'), + italics: path.join(__dirname, '../../public/utils/opensans/OpenSans-Italic.ttf'), + bolditalics: path.join(__dirname, '../../public/utils/opensans/OpenSans-BoldItalic.ttf'), + monslight: path.join(__dirname, '../../public/utils/opensans/Montserrat-Light.ttf') } }; + this.vulnerabilityRequest = new VulnerabilityRequest(this.server); + this.overviewRequest = new OverviewRequest(this.server); + this.rootcheckRequest = new RootcheckRequest(this.server); + this.pciRequest = new PciRequest(this.server); + this.gdprRequest = new GdprRequest(this.server); + this.auditRequest = new AuditRequest(this.server); + this.syscheckRequest = new SyscheckRequest(this.server); + this.printer = new PdfPrinter(this.fonts); this.dd = { styles: { - rightme: { - alignment: 'right' - }, - centerme: { - alignment: 'center' - }, - title: { + h1: { fontSize: 22, - bold: true + monslight: true, + color: '#1ea5c8' + }, + h2: { + fontSize: 18, + monslight: true, + color: '#1ea5c8' }, - subtitle: { + h3: { fontSize: 16, - bold: true + monslight: true, + color: '#1ea5c8' }, - subtitlenobold: { - fontSize: 12 + h4: { + fontSize: 14, + monslight: true, + color: '#1ea5c8' }, - quote: { - color: 'gray', - italics: true + standard: { + color: '#333' + }, + whiteColorFilters: { + color: '#FFF', + fontSize: 14 + }, + whiteColor: { + color: '#FFF' } }, + pageMargins: [40, 80, 40, 80], header: { + margin: [40, 20, 0, 0], columns: [ - { image: path.join(__dirname, '../../public/img/logo.png'), fit: [190, 90], style: 'rightme', margin: [0, 10, 0, 0] }, + { + image: path.join(__dirname, '../../public/img/logo.png'), + width: 190 + }, + { text: 'info@wazuh.com\nhttps://wazuh.com', alignment: 'right', margin: [0, 0, 40, 0], color: '#1EA5C8' } ] }, content: [ + ], - footer: { - columns: [ - { text: 'Copyright © 2018 Wazuh, Inc.', alignment: 'right', fontSize: 10, margin: [0, 0, 10, 0] } - ] + footer: function (currentPage, pageCount) { + return { + columns: [ + { + text: 'Copyright © 2018 Wazuh, Inc.', color: '#1EA5C8', margin: [40, 40, 0, 0] + }, + { + text: 'Page ' + currentPage.toString() + ' of ' + pageCount, alignment: 'right', margin: [0, 40, 40, 0], color: '#1EA5C8' + } + ] + }; }, pageBreakBefore: function (currentNode, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage) { - //check if signature part is completely on the last page, add pagebreak if not - if (currentNode.id === 'signature' && (currentNode.pageNumbers.length != 1 || currentNode.pageNumbers[0] != currentNode.pages)) { - return true; + if (currentNode.id && currentNode.id.includes('splitvis')) { + return followingNodesOnPage.length === 6 || followingNodesOnPage.length === 7; } - //check if last paragraph is entirely on a single page, add pagebreak if not - else if (currentNode.id === 'closingParagraph' && currentNode.pageNumbers.length != 1) { - return true; + if ((currentNode.id && currentNode.id.includes('splitsinglevis')) || + (currentNode.id && currentNode.id.includes('singlevis')) + ) { + return followingNodesOnPage.length === 6; } + return false; } }; + + this.apiRequest = new WazuhApi(server); } renderTables(tables) { for (const table of tables) { const rowsparsed = rawParser(table.rawResponse, table.columns); if (Array.isArray(rowsparsed) && rowsparsed.length) { - const rows = rowsparsed.length > 100 ? rowsparsed.slice(0,99) : rowsparsed; - this.dd.content.push({ text: table.title, style: 'subtitlenobold', pageBreak: 'before' }); - + const rows = rowsparsed.length > 100 ? rowsparsed.slice(0, 99) : rowsparsed; + this.dd.content.push({ text: table.title, style: 'h3', pageBreak: 'before' }); + this.dd.content.push('\n'); const full_body = []; const sortFunction = (a, b) => parseInt(a[a.length - 1]) < parseInt(b[b.length - 1]) ? 1 : parseInt(a[a.length - 1]) > parseInt(b[b.length - 1]) ? -1 : 0; + + TimSort.sort(rows, sortFunction); - const widths = Array(table.columns.length).fill('*'); - - full_body.push(table.columns, ...rows); + + const modifiedRows = []; + for (const row of rows) { + modifiedRows.push(row.map(cell => { + return { text: cell, style: 'standard' }; + })); + } + + const widths = Array(table.columns.length - 1).fill('auto'); + widths.push('*'); + + full_body.push( + table.columns.map(col => { + return { text: col, style: 'whiteColor', border: [0, 0, 0, 0] }; + }) + , ...modifiedRows); this.dd.content.push({ - fontSize:8, + fontSize: 8, table: { + headerRows: 1, widths, body: full_body }, - layout: 'lightHorizontalLines' + layout: { + fillColor: i => i === 0 ? '#78C8DE' : null, + hLineColor: () => '#78C8DE', + hLineWidth: () => 1, + vLineWidth: () => 0 + } + }); this.dd.content.push('\n'); } } } - renderTimeRange(from,to) { + renderTimeRangeAndFilters(from, to, filters) { const str = `${from} to ${to}`; this.dd.content.push({ - columns: [ - { - - image: path.join(__dirname, '../reporting/clock.png'), - width: 10, - height: 10 - }, - { - margin: [5, 0, 0, 0], - text: str, - fontSize: 10 - } - ] + fontSize: 8, + table: { + widths: ['*'], + body: [ + [{ + columns: [ + { + image: clockIconRaw, + width: 10, + height: 10, + margin: [40, 4, 0, 0] + }, + { + text: str, margin: [43, 0, 0, 0], style: 'whiteColorFilters' + } + ] + }], + [{ + columns: [ + { + image: filterIconRaw, + width: 10, + height: 10, + margin: [40, 4, 0, 0] + }, + { + text: filters, margin: [43, 0, 0, 0], style: 'whiteColorFilters' + } + ] + }]] + }, + margin: [-40, 0, -40, 0], + layout: { + fillColor: () => '#78C8DE', + hLineWidth: () => 0, + vLineWidth: () => 0 + } }); - this.dd.content.push('\n'); + + this.dd.content.push({ text: '\n' }); } - renderFilters(filters, searchBar) { + sanitizeFilters(filters, searchBar) { let str = ''; + const len = filters.length; for (let i = 0; i < len; i++) { const filter = filters[i]; + str += i === len - 1 ? - (filter.meta.negate ? 'NOT ' : '' ) + filter.meta.key + ': ' + filter.meta.value : - (filter.meta.negate ? 'NOT ' : '' ) + filter.meta.key + ': ' + filter.meta.value + ' AND '; + (filter.meta.negate ? 'NOT ' : '' ) + filter.meta.key + ': ' + filter.meta.value : + (filter.meta.negate ? 'NOT ' : '' ) + filter.meta.key + ': ' + filter.meta.value + ' AND '; } if (searchBar) { str += ' AND ' + searchBar; } - this.dd.content.push({ - columns: [{ - image: path.join(__dirname, '../reporting/filters.png'), - width: 10, - height: 10 - },{ - margin: [5, 0, 0, 0], - text: str, - fontSize: 10 - }] - }); - this.dd.content.push('\n'); + return str; } - renderHeader(section, tab, isAgents) { - if (section && typeof section === 'string') { - this.dd.content.push({ - text: descriptions[tab].title + ' report', style: 'title' - }); - this.dd.content.push('\n'); - } + async renderHeader(section, tab, isAgents, apiId) { + try { + if (section && typeof section === 'string') { + this.dd.content.push({ + text: descriptions[tab].title + ' report', style: 'h1' + }); + this.dd.content.push('\n'); + } - if(isAgents && typeof isAgents === 'string'){ - this.dd.content.push({text: `Report for agent ${isAgents}`, style:'subtitlenobold'}); - this.dd.content.push('\n'); - } + if (isAgents && typeof isAgents === 'string') { + const agent = await this.apiRequest.makeGenericRequest('GET', `/agents/${isAgents}`, {}, apiId); + if (agent && agent.data && typeof agent.data.status === 'string' && agent.data.status !== 'Active') { + this.dd.content.push({ text: `Warning. Agent is ${agent.data.status.toLowerCase()}`, style: 'standard' }); + this.dd.content.push('\n'); + } + await this.buildAgentsTable([isAgents], apiId); - if(descriptions[tab] && descriptions[tab].description){ - this.dd.content.push({ text: descriptions[tab].description, style: 'quote' }); - this.dd.content.push('\n'); + let dateAddStr = ''; + if (agent && agent.data && agent.data.dateAdd) { + dateAddStr = `Registration date: ${agent.data.dateAdd}.`; + } + + let dateLastKeepAlive = ''; + if (agent && agent.data && agent.data.lastKeepAlive) { + dateLastKeepAlive += `Last keep alive: ${agent.data.lastKeepAlive}.`; + } + + if (dateAddStr.length) { + this.dd.content.push({ text: dateAddStr, style: 'standard' }); + this.dd.content.push('\n'); + } + + if (dateLastKeepAlive.length) { + this.dd.content.push({ text: dateLastKeepAlive, style: 'standard' }); + this.dd.content.push('\n'); + } + + if (agent && agent.data && agent.data.group) { + this.dd.content.push({ text: `Group: ${agent.data.group}`, style: 'standard' }); + this.dd.content.push('\n'); + } + } + + if (descriptions[tab] && descriptions[tab].description) { + this.dd.content.push({ text: descriptions[tab].description, style: 'standard' }); + this.dd.content.push('\n'); + } + + return; + } catch (error) { + return Promise.reject(error); } } checkTitle(item, isAgents, tab) { const title = isAgents ? - AgentsVisualizations[tab].filter(v => v._id === item.id) : - OverviewVisualizations[tab].filter(v => v._id === item.id); + AgentsVisualizations[tab].filter(v => v._id === item.id) : + OverviewVisualizations[tab].filter(v => v._id === item.id); return title; } @@ -193,8 +316,8 @@ export default class WazuhReportingCtrl { for (const item of single_vis) { const title = this.checkTitle(item, isAgents, tab); - this.dd.content.push({ text: title[0]._source.title, style: 'subtitlenobold' }); - this.dd.content.push({ columns: [ { image: item.element, width: 500 } ] }); + this.dd.content.push({ id: 'singlevis' + title[0]._source.title, text: title[0]._source.title, style: 'h3' }); + this.dd.content.push({ columns: [{ image: item.element, width: 500 }] }); this.dd.content.push('\n'); } @@ -208,8 +331,8 @@ export default class WazuhReportingCtrl { this.dd.content.push({ columns: [ - { text: title_1[0]._source.title, style: 'subtitlenobold', width: 280 }, - { text: title_2[0]._source.title, style: 'subtitlenobold', width: 280 } + { id: 'splitvis' + title_1[0]._source.title, text: title_1[0]._source.title, style: 'h3', width: 280 }, + { id: 'splitvis' + title_2[0]._source.title, text: title_2[0]._source.title, style: 'h3', width: 280 } ] }); @@ -231,66 +354,493 @@ export default class WazuhReportingCtrl { const title = this.checkTitle(item, isAgents, tab); this.dd.content.push({ columns: [ - { text: title[0]._source.title, style: 'subtitlenobold', width: 280 } + { id: 'splitsinglevis' + title[0]._source.title, text: title[0]._source.title, style: 'h3', width: 280 } ] }); this.dd.content.push({ columns: [{ image: item.element, width: 280 }] }); this.dd.content.push('\n'); } } - + + async buildAgentsTable(ids, apiId) { + if (!ids || !ids.length) return; + try { + const rows = []; + for (const item of ids) { + let data = false; + try { + const agent = await this.apiRequest.makeGenericRequest('GET', `/agents/${item}`, {}, apiId); + if (agent && agent.data) { + data = {}; + Object.assign(data, agent.data); + } + } catch (error) { + continue; + } + const str = Array(6).fill('---'); + str[0] = item; + if (data && data.name) str[1] = data.name; + if (data && data.ip) str[2] = data.ip; + if (data && data.version) str[3] = data.version; + if (data && data.manager_host) str[4] = data.manager_host; + if (data && data.os && data.os.name && data.os.version) str[5] = `${data.os.name} ${data.os.version}`; + rows.push(str); + } + + PdfTable(this.dd, rows, ['ID', 'Name', 'IP', 'Version', 'Manager', 'OS'], null, null, true); + + this.dd.content.push('\n'); + } catch (error) { + return Promise.reject(error); + } + } + + async extendedInformation(section, tab, apiId, from, to, filters, pattern = 'wazuh-alerts-3.x-*', agent = null) { + try { + if (section === 'agents' && !agent) { + throw new Error('Reporting for specific agent needs an agent ID in order to work properly'); + } + + const agents = await this.apiRequest.makeGenericRequest('GET', '/agents', { limit: 1 }, apiId); + const totalAgents = agents.data.totalItems; + + if (section === 'overview' && tab === 'vuls') { + const low = await this.vulnerabilityRequest.uniqueSeverityCount(from, to, 'Low', filters, pattern); + const medium = await this.vulnerabilityRequest.uniqueSeverityCount(from, to, 'Medium', filters, pattern); + const high = await this.vulnerabilityRequest.uniqueSeverityCount(from, to, 'High', filters, pattern); + const critical = await this.vulnerabilityRequest.uniqueSeverityCount(from, to, 'Critical', filters, pattern); + + this.dd.content.push({ text: 'Summary', style: 'h2' }); + this.dd.content.push('\n'); + const ulcustom = [`${critical + high + medium + low} of ${totalAgents} agents have vulnerabilities.`]; + if (critical) ulcustom.push(`${critical} of ${totalAgents} agents have critical vulnerabilities.`); + if (high) ulcustom.push(`${high} of ${totalAgents} agents have high vulnerabilities.`); + if (medium) ulcustom.push(`${medium} of ${totalAgents} agents have medium vulnerabilities.`); + if (low) ulcustom.push(`${low} of ${totalAgents} agents have low vulnerabilities.`); + + this.dd.content.push({ + ul: ulcustom + }); + this.dd.content.push('\n'); + + const lowRank = await this.vulnerabilityRequest.topAgentCount(from, to, 'Low', filters, pattern); + const mediumRank = await this.vulnerabilityRequest.topAgentCount(from, to, 'Medium', filters, pattern); + const highRank = await this.vulnerabilityRequest.topAgentCount(from, to, 'High', filters, pattern); + const criticalRank = await this.vulnerabilityRequest.topAgentCount(from, to, 'Critical', filters, pattern); + + if (criticalRank.length) { + this.dd.content.push({ text: 'Top 3 agents with critical severity vulnerabilities', style: 'h3' }); + this.dd.content.push('\n'); + await this.buildAgentsTable(criticalRank, apiId); + this.dd.content.push('\n'); + } + + if (highRank.length) { + this.dd.content.push({ text: 'Top 3 agents with high severity vulnerabilities', style: 'h3' }); + this.dd.content.push('\n'); + await this.buildAgentsTable(highRank, apiId); + this.dd.content.push('\n'); + } + + if (mediumRank.length) { + this.dd.content.push({ text: 'Top 3 agents with medium severity vulnerabilities', style: 'h3' }); + this.dd.content.push('\n'); + await this.buildAgentsTable(mediumRank, apiId); + this.dd.content.push('\n'); + } + + if (lowRank.length) { + this.dd.content.push({ text: 'Top 3 agents with low severity vulnerabilities', style: 'h3' }); + this.dd.content.push('\n'); + await this.buildAgentsTable(lowRank, apiId); + this.dd.content.push('\n'); + } + + const cveRank = await this.vulnerabilityRequest.topCVECount(from, to, filters, pattern); + if (cveRank.length) { + this.dd.content.push({ text: 'Top 3 CVE', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ ol: cveRank }); + this.dd.content.push('\n'); + } + } + + if (section === 'overview' && tab === 'general') { + const level15Rank = await this.overviewRequest.topLevel15(from, to, filters, pattern); + if (level15Rank.length) { + this.dd.content.push({ text: 'Top 3 agents with level 15 alerts', style: 'h2' }); + await this.buildAgentsTable(level15Rank, apiId); + } + } + + if (section === 'overview' && tab === 'pm') { + const top5RootkitsRank = await this.rootcheckRequest.top5RootkitsDetected(from, to, filters, pattern); + if (top5RootkitsRank.length) { + this.dd.content.push({ text: 'Most common rootkits found among your agents', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ text: 'Rootkits are a set of software tools that enable an unauthorized user to gain control of a computer system without being detected.', style: 'standard' }); + this.dd.content.push('\n'); + this.dd.content.push({ ol: top5RootkitsRank }); + this.dd.content.push('\n'); + } + + const hiddenPids = await this.rootcheckRequest.agentsWithHiddenPids(from, to, filters, pattern); + hiddenPids && this.dd.content.push({ text: `${hiddenPids} of ${totalAgents} agents have hidden processes`, style: 'h3' }); + !hiddenPids && this.dd.content.push({ text: `No agents have hidden processes`, style: 'h3' }); + this.dd.content.push('\n'); + + const hiddenPorts = await this.rootcheckRequest.agentsWithHiddenPorts(from, to, filters, pattern); + hiddenPorts && this.dd.content.push({ text: `${hiddenPorts} of ${totalAgents} agents have hidden ports`, style: 'h3' }); + !hiddenPorts && this.dd.content.push({ text: `No agents have hidden ports`, style: 'h3' }); + this.dd.content.push('\n'); + } + + if (['overview', 'agents'].includes(section) && tab === 'pci') { + const topPciRequirements = await this.pciRequest.topPCIRequirements(from, to, filters, pattern); + this.dd.content.push({ text: 'Most common PCI DSS requirements alerts found', style: 'h2' }); + this.dd.content.push('\n'); + for (const item of topPciRequirements) { + const rules = await this.pciRequest.getRulesByRequirement(from, to, filters, item, pattern); + this.dd.content.push({ text: `Requirement ${item}`, style: 'h3' }); + this.dd.content.push('\n'); + + if (PCI[item]) { + const content = typeof PCI[item] === 'string' ? + { text: PCI[item], style: 'standard' } : + PCI[item]; + this.dd.content.push(content); + this.dd.content.push('\n'); + } + + rules && rules.length && PdfTable(this.dd, rules, ['Rule ID', 'Description'], ['ruleId', 'ruleDescription'], `Top rules for ${item} requirement`); + this.dd.content.push('\n'); + } + } + + if (['overview', 'agents'].includes(section) && tab === 'gdpr') { + const topGdprRequirements = await this.gdprRequest.topGDPRRequirements(from, to, filters, pattern); + this.dd.content.push({ text: 'Most common GDPR requirements alerts found', style: 'h2' }); + this.dd.content.push('\n'); + for (const item of topGdprRequirements) { + const rules = await this.gdprRequest.getRulesByRequirement(from, to, filters, item, pattern); + this.dd.content.push({ text: `Requirement ${item}`, style: 'h3' }); + this.dd.content.push('\n'); + + if (GDPR && GDPR[item]) { + const content = typeof GDPR[item] === 'string' ? + { text: GDPR[item], style: 'standard' } : + GDPR[item]; + this.dd.content.push(content); + this.dd.content.push('\n'); + } + + rules && rules.length && PdfTable(this.dd, rules, ['Rule ID', 'Description'], ['ruleId', 'ruleDescription'], `Top rules for ${item} requirement`); + this.dd.content.push('\n'); + } + this.dd.content.push('\n'); + } + + if (section === 'overview' && tab === 'audit') { + const auditAgentsNonSuccess = await this.auditRequest.getTop3AgentsSudoNonSuccessful(from, to, filters, pattern); + if (auditAgentsNonSuccess && auditAgentsNonSuccess.length) { + this.dd.content.push({ text: 'Agents with high number of failed sudo commands', style: 'h2' }); + await this.buildAgentsTable(auditAgentsNonSuccess, apiId); + } + const auditAgentsFailedSyscall = await this.auditRequest.getTop3AgentsFailedSyscalls(from, to, filters, pattern); + if (auditAgentsFailedSyscall && auditAgentsFailedSyscall.length) { + this.dd.content.push({ text: 'Most common failing syscalls', style: 'h2' }); + this.dd.content.push('\n'); + PdfTable(this.dd, auditAgentsFailedSyscall, ['Agent ID', 'Syscall ID', 'Syscall'], ['agent', 'syscall.id', 'syscall.syscall'], null, false); + this.dd.content.push('\n'); + } + } + + if (section === 'overview' && tab === 'fim') { + const rules = await this.syscheckRequest.top3Rules(from, to, filters, pattern); + + if (rules && rules.length) { + this.dd.content.push({ text: 'Top 3 FIM rules', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ + text: 'Top 3 rules that are generating most alerts.', + style: 'standard' + }); + this.dd.content.push('\n'); + PdfTable(this.dd, rules, ['Rule ID', 'Description'], ['ruleId', 'ruleDescription'], null); + this.dd.content.push('\n'); + } + + const agents = await this.syscheckRequest.top3agents(from, to, filters, pattern); + + if (agents && agents.length) { + this.dd.content.push({ text: 'Agents with suspicious FIM activity', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ + text: 'Top 3 agents that have most FIM alerts from level 7 to level 15. Take care about them.', + style: 'standard' + }); + this.dd.content.push('\n'); + await this.buildAgentsTable(agents, apiId); + } + } + + if (section === 'agents' && tab === 'pm') { + const database = await this.apiRequest.makeGenericRequest('GET', `/rootcheck/${agent}`, { limit: 15 }, apiId); + const cis = await this.apiRequest.makeGenericRequest('GET', `/rootcheck/${agent}/cis`, {}, apiId); + const pci = await this.apiRequest.makeGenericRequest('GET', `/rootcheck/${agent}/pci`, {}, apiId); + const lastScan = await this.apiRequest.makeGenericRequest('GET', `/rootcheck/${agent}/last_scan`, {}, apiId); + + if (lastScan && lastScan.data) { + if (lastScan.data.start && lastScan.data.end) { + this.dd.content.push({ text: `Last policy monitoring scan was executed from ${lastScan.data.start} to ${lastScan.data.end}.`, style: 'standard' }); + } else if (lastScan.data.start) { + this.dd.content.push({ text: `Policy monitoring scan is currently in progress for this agent (started on ${lastScan.data.start}).`, style: 'standard' }); + } else { + this.dd.content.push({ text: `Policy monitoring scan is currently in progress for this agent.`, style: 'standard' }); + } + this.dd.content.push('\n'); + } + + if (database && database.data && database.data.items) { + PdfTable(this.dd, database.data.items, ['Date', 'Status', 'Event'], ['readDay', 'status', 'event'], 'Last entries from policy monitoring scan'); + this.dd.content.push('\n'); + } + + if (pci && pci.data && pci.data.items) { + this.dd.content.push({ text: 'Fired rules due to PCI requirements', style: 'h2', pageBreak: 'before' }); + this.dd.content.push('\n'); + for (const item of pci.data.items) { + const rules = await this.pciRequest.getRulesByRequirement(from, to, filters, item, pattern); + this.dd.content.push({ text: `Requirement ${item}`, style: 'h3' }); + this.dd.content.push('\n'); + + if (PCI[item]) { + const content = typeof PCI[item] === 'string' ? + { text: PCI[item], style: 'standard' } : + PCI[item]; + this.dd.content.push(content); + this.dd.content.push('\n'); + } + + PdfTable(this.dd, rules, ['Rule ID', 'Description'], ['ruleId', 'ruleDescription']); + this.dd.content.push('\n'); + } + } + + const top5RootkitsRank = await this.rootcheckRequest.top5RootkitsDetected(from, to, filters, pattern, 10); + if (top5RootkitsRank && top5RootkitsRank.length) { + this.dd.content.push({ text: 'Rootkits files found', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ text: 'Rootkits are a set of software tools that enable an unauthorized user to gain control of a computer system without being detected.', style: 'standard' }); + this.dd.content.push('\n'); + this.dd.content.push({ ol: top5RootkitsRank }); + this.dd.content.push('\n'); + } + + } + + if (section === 'agents' && tab === 'audit') { + const auditFailedSyscall = await this.auditRequest.getTopFailedSyscalls(from, to, filters, pattern); + auditFailedSyscall && auditFailedSyscall.length && PdfTable(this.dd, auditFailedSyscall, ['Syscall ID', 'Syscall'], ['id', 'syscall'], 'Most common failing syscalls'); + this.dd.content.push('\n'); + } + + if (section === 'agents' && tab === 'fim') { + const lastScan = await this.apiRequest.makeGenericRequest('GET', `/syscheck/${agent}/last_scan`, {}, apiId); + + if (lastScan && lastScan.data) { + if (lastScan.data.start && lastScan.data.end) { + this.dd.content.push({ text: `Last file integrity monitoring scan was executed from ${lastScan.data.start} to ${lastScan.data.end}.` }); + } else if (lastScan.data.start) { + this.dd.content.push({ text: `File integrity monitoring scan is currently in progress for this agent (started on ${lastScan.data.start}).` }); + } else { + this.dd.content.push({ text: `File integrity monitoring scan is currently in progress for this agent.` }); + } + this.dd.content.push('\n'); + } + + const lastTenDeleted = await this.syscheckRequest.lastTenDeletedFiles(from, to, filters, pattern); + lastTenDeleted && lastTenDeleted.length && PdfTable(this.dd, lastTenDeleted, ['Path', 'Date'], ['path', 'date'], 'Last 10 deleted files'); + this.dd.content.push('\n'); + + const lastTenModified = await this.syscheckRequest.lastTenModifiedFiles(from, to, filters, pattern); + lastTenModified && lastTenModified.length && PdfTable(this.dd, lastTenModified, ['Path', 'Date'], ['path', 'date'], 'Last 10 modified files'); + this.dd.content.push('\n'); + } + + if (section === 'agents' && tab === 'syscollector') { + + const hardware = await this.apiRequest.makeGenericRequest('GET', `/syscollector/${agent}/hardware`, {}, apiId); + + if (hardware && hardware.data) { + this.dd.content.push({ text: 'Hardware information', style: 'h2' }); + this.dd.content.push('\n'); + const ulcustom = []; + if (hardware.data.cpu && hardware.data.cpu.cores) ulcustom.push(hardware.data.cpu.cores + ' cores '); + if (hardware.data.cpu && hardware.data.cpu.name) ulcustom.push(hardware.data.cpu.name); + if (hardware.data.ram && hardware.data.ram.total) ulcustom.push(Math.round(((hardware.data.ram.total / 1024) / 1024), 2) + 'GB RAM'); + ulcustom && ulcustom.length && this.dd.content.push({ + ul: ulcustom + }); + this.dd.content.push('\n'); + } + + const os = await this.apiRequest.makeGenericRequest('GET', `/syscollector/${agent}/os`, {}, apiId); + + if (os && os.data) { + this.dd.content.push({ text: 'OS information', style: 'h2' }); + this.dd.content.push('\n'); + const ulcustom = []; + if (os.data.sysname) ulcustom.push(os.data.sysname); + if (os.data.version) ulcustom.push(os.data.version); + if (os.data.architecture) ulcustom.push(os.data.architecture); + if (os.data.release) ulcustom.push(os.data.release); + if (os.data.os && os.data.os.name && os.data.os.version) ulcustom.push(os.data.os.name + ' ' + os.data.os.version); + ulcustom && ulcustom.length && this.dd.content.push({ + ul: ulcustom + }); + this.dd.content.push('\n'); + } + + const topCriticalPackages = await this.vulnerabilityRequest.topPackages(from, to, 'Critical', filters, pattern); + const topHighPackages = await this.vulnerabilityRequest.topPackages(from, to, 'High', filters, pattern); + + const affected = []; + affected.push(...topCriticalPackages, ...topHighPackages); + if (affected && affected.length) { + this.dd.content.push({ text: 'Vulnerable packages found (last 24 hours)', style: 'h2' }); + this.dd.content.push('\n'); + PdfTable(this.dd, affected, ['Package', 'Severity'], ['package', 'severity'], null); + this.dd.content.push('\n'); + } + } + + + if (section === 'agents' && tab === 'vuls') { + const topCriticalPackages = await this.vulnerabilityRequest.topPackagesWithCVE(from, to, 'Critical', filters, pattern); + if (topCriticalPackages && topCriticalPackages.length) { + this.dd.content.push({ text: 'Critical severity', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ text: 'These vulnerabilties are critical, please review your agent. Click on each link to read more about each found vulnerability.', style: 'standard' }); + this.dd.content.push('\n'); + const customul = []; + for (const critical of topCriticalPackages) { + customul.push({ text: critical.package, style: 'standard' }); + customul.push({ + ul: critical.references.map(item => { return { text: item, color: '#1EA5C8' }; }) + }); + } + this.dd.content.push({ ul: customul }) + this.dd.content.push('\n'); + } + + const topHighPackages = await this.vulnerabilityRequest.topPackagesWithCVE(from, to, 'High', filters, pattern); + if (topHighPackages && topHighPackages.length) { + this.dd.content.push({ text: 'High severity', style: 'h2' }); + this.dd.content.push('\n'); + this.dd.content.push({ text: 'Click on each link to read more about each found vulnerability.', style: 'standard' }); + this.dd.content.push('\n'); + const customul = []; + for (const critical of topHighPackages) { + customul.push({ text: critical.package, style: 'standard' }); + customul.push({ + ul: critical.references.map(item => { return { text: item, color: '#1EA5C8' }; }) + }); + } + customul && customul.length && this.dd.content.push({ ul: customul }); + this.dd.content.push('\n'); + } + + } + + return false; + } catch (error) { + return Promise.reject(error); + } + } + async report(req, reply) { try { + // Init - this.printer = new PdfPrinter(this.fonts); + this.printer = new PdfPrinter(this.fonts); this.dd.content = []; - if (!fs.existsSync(path.join(__dirname, '../../../../optimize/wazuh-reporting'))) { - fs.mkdirSync(path.join(__dirname, '../../../../optimize/wazuh-reporting')); + if (!fs.existsSync(path.join(__dirname, REPORTING_PATH))) { + fs.mkdirSync(path.join(__dirname, REPORTING_PATH)); } if (req.payload && req.payload.array) { + const name = req.payload.name; const tab = req.payload.tab; + const section = req.payload.section; + const apiId = req.headers && req.headers.id ? req.headers.id : false; + const pattern = req.headers && req.headers.pattern ? req.headers.pattern : false; + const kfilters = req.payload.filters; + const isAgents = req.payload.isAgents; + const from = req.payload.time && req.payload.time.from ? req.payload.time.from : false; + const to = req.payload.time && req.payload.time.to ? req.payload.time.to : false; + + if (!tab) throw new Error('Reporting needs a valid app tab in order to work properly'); + if (!section) throw new Error('Reporting needs a valid app section in order to work properly'); + if (!apiId) throw new Error('Reporting needs a valid Wazuh API ID in order to work properly'); + if (!name) throw new Error('Reporting needs a valid file name in order to work properly'); + + const isSycollector = tab === 'syscollector'; + + await this.renderHeader(section, tab, isAgents, apiId); + + let filters = false; + if (kfilters) { + filters = this.sanitizeFilters(kfilters, req.payload.searchBar); + } - this.renderHeader(req.payload.section, tab, req.payload.isAgents); - - if (req.payload.time) { - this.renderTimeRange(req.payload.time.from, req.payload.time.to); + if (!isSycollector && req.payload.time && filters) { + this.renderTimeRangeAndFilters(from, to, filters); } - if (req.payload.filters) { - this.renderFilters(req.payload.filters, req.payload.searchBar); + if (req.payload.time || isSycollector) { + await this.extendedInformation( + section, + tab, + apiId, + isSycollector ? from : new Date(from) - 1, + isSycollector ? to : new Date(to) - 1, + isSycollector ? filters + ' AND rule.groups: "vulnerability-detector"' : filters, + pattern, + isAgents + ); } - this.renderVisualizations(req.payload.array, req.payload.isAgents, tab) + !isSycollector && this.renderVisualizations(req.payload.array, isAgents, tab); - if (req.payload.tables) { - this.renderTables(req.payload.tables); + if (!isSycollector && req.payload.tables) { + this.renderTables(req.payload.tables); } const pdfDoc = this.printer.createPdfKitDocument(this.dd); - await pdfDoc.pipe(fs.createWriteStream(path.join(__dirname, '../../../../optimize/wazuh-reporting/' + req.payload.name))); + await pdfDoc.pipe(fs.createWriteStream(path.join(__dirname, REPORTING_PATH + '/' + req.payload.name))); pdfDoc.end(); } return reply({ error: 0, data: null }); } catch (error) { // Delete generated file if an error occurred if (req && req.payload && req.payload.name && - fs.existsSync(path.join(__dirname, '../../../../optimize/wazuh-reporting/' + req.payload.name)) + fs.existsSync(path.join(__dirname, REPORTING_PATH + '/' + req.payload.name)) ) { - fs.unlinkSync(path.join(__dirname, '../../../../optimize/wazuh-reporting/' + req.payload.name)); + fs.unlinkSync(path.join(__dirname, REPORTING_PATH + '/' + req.payload.name)); } return ErrorResponse(error.message || error, 5029, 500, reply); } } - async getReports(req,reply) { + async getReports(req, reply) { try { - if (!fs.existsSync(path.join(__dirname, '../../../../optimize/wazuh-reporting'))) { - fs.mkdirSync(path.join(__dirname, '../../../../optimize/wazuh-reporting')); + if (!fs.existsSync(path.join(__dirname, REPORTING_PATH))) { + fs.mkdirSync(path.join(__dirname, REPORTING_PATH)); } const list = []; - const reportDir = path.join(__dirname, '../../../../optimize/wazuh-reporting'); - const sortFunction = (a,b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0; + const reportDir = path.join(__dirname, REPORTING_PATH); + const sortFunction = (a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0; fs.readdirSync(reportDir).forEach(file => { const stats = fs.statSync(reportDir + '/' + file); file = { @@ -300,25 +850,27 @@ export default class WazuhReportingCtrl { }; list.push(file); }); - TimSort.sort(list,sortFunction) - return reply({list: list}); + TimSort.sort(list, sortFunction) + return reply({ list: list }); } catch (error) { return ErrorResponse(error.message || error, 5031, 500, reply); } } - async getReportByName(req,reply) { + async getReportByName(req, reply) { try { - return reply.file(path.join(__dirname, '../../../../optimize/wazuh-reporting/' + req.params.name)); + if (!req.params || !req.params.name) throw new Error('Invalid file name'); + return reply.file(path.join(__dirname, REPORTING_PATH + '/' + req.params.name)); } catch (error) { return ErrorResponse(error.message || error, 5030, 500, reply); } } - async deleteReportByName(req,reply) { + async deleteReportByName(req, reply) { try { - fs.unlinkSync(path.join(__dirname, '../../../../optimize/wazuh-reporting/' + req.params.name)); - return reply({error:0}); + if (!req.params || !req.params.name) throw new Error('Invalid file name'); + fs.unlinkSync(path.join(__dirname, REPORTING_PATH + '/' + req.params.name)); + return reply({ error: 0 }); } catch (error) { return ErrorResponse(error.message || error, 5032, 500, reply); } diff --git a/server/integration-files/gdpr-requirements-pdfmake.js b/server/integration-files/gdpr-requirements-pdfmake.js new file mode 100644 index 0000000000..0294104ce9 --- /dev/null +++ b/server/integration-files/gdpr-requirements-pdfmake.js @@ -0,0 +1,24 @@ +/* + * Wazuh app - Module for GDPR requirements (Reporting) + * Copyright (C) 2018 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 default { + "II_5.1.f": "Ensure the ongoing confidentiality, integrity, availability and resilience of processing systems and services, verifying its modifications, accesses, locations and guarantee the safety of them. File sharing protection and file sharing technologies that meet the requirements of data protection.", + "III_14.2.c": " Restrict the processing of personal data temporarily.", + "III_17": " Permanently erase personal information of a subject.", + "IV_24.2": "Be able to demonstrate compliance with the GDPR by complying with data protection policies.", + "IV_28": " Ensure data protection during processing, through technical and organizational measures.", + "IV_30.1.g": "It is necessary to keep all processing activities documented, to carry out an inventory of data from beginning to end and an audit, in order to know all the places where personal and sensitive data are located, processed, stored or transmitted.", + "IV_32.1.c": "Data Loss Prevention (DLP) capabilities to examine data flows and identify personal data that is not subject to adequate safeguards or authorizations. DLP tools can block or quarantine such data flows. Classify current data appropriately to determine specific categories of data that will be subject to the GDPR.", + "IV_32.2": "Account management tools that closely monitor actions taken by standard administrators and users who use standard or privileged account credentials are required to control access to data. ", + "IV_33": " Notify the supervisory authority of a violation of the data in 72 hours and in certain cases, the injured parties.", + "IV_35.1": "Perform a data protection impact evaluation for high risk processes. Implement appropriate technical measures to safeguard the rights and freedoms of data subjects, informed by an assessment of the risks to these rights and freedoms.", + "IV_35.7.d": "Capabilities for identification, blocking and forensic investigation of data breaches by malicious actors, through compromised credentials, unauthorized network access, persistent threats and verification of the correct operation of all components. Network perimeter and endpoint security tools to prevent unauthorized access to the network, prevent the entry of unwanted data types and malicious threats. Anti-malware and anti-ransomware to prevent malware and ransomware threats from entering your devices. A behavioral analysis that uses machine intelligence to identify people who do anomalous things on the network, in order to give early visibility and alert employees who start to become corrupt." +}; \ No newline at end of file diff --git a/server/integration-files/gdpr-requirements.js b/server/integration-files/gdpr-requirements.js index a8cd53614c..e93c5b205b 100644 --- a/server/integration-files/gdpr-requirements.js +++ b/server/integration-files/gdpr-requirements.js @@ -21,4 +21,4 @@ export default { "IV_33": " Notify the supervisory authority of a violation of the data in 72 hours and in certain cases, the injured parties.", "IV_35.1": "Perform a data protection impact evaluation for high risk processes. Implement appropriate technical measures to safeguard the rights and freedoms of data subjects, informed by an assessment of the risks to these rights and freedoms.", "IV_35.7.d": "Capabilities for identification, blocking and forensic investigation of data breaches by malicious actors, through compromised credentials, unauthorized network access, persistent threats and verification of the correct operation of all components.
Network perimeter and endpoint security tools to prevent unauthorized access to the network, prevent the entry of unwanted data types and malicious threats. Anti-malware and anti-ransomware to prevent malware and ransomware threats from entering your devices.
A behavioral analysis that uses machine intelligence to identify people who do anomalous things on the network, in order to give early visibility and alert employees who start to become corrupt." -} +}; \ No newline at end of file diff --git a/server/integration-files/pci-requirements-pdfmake.js b/server/integration-files/pci-requirements-pdfmake.js new file mode 100644 index 0000000000..fcdb65b9d6 --- /dev/null +++ b/server/integration-files/pci-requirements-pdfmake.js @@ -0,0 +1,150 @@ +/* + * Wazuh app - Module for PCI requirements (Reporting) + * Copyright (C) 2018 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 default { + "1.1.1": "A formal process for approving and testing all network connections and changes to the firewall and router configurations", + "1.3.4": "Do not allow unauthorized outbound traffic from the cardholder data environment to the Internet.", + "1.4": { + stack: [ + "Install personal firewall software or equivalent functionality on any portable computing devices (including company and/or employee-owned) that connect to the Internet when outside the network (for example, laptops used by employees), and which are also used to access the CDE. Firewall (or equivalent) configurations include:", + {text:'\n'}, + { + ul: [ + "Specific configuration settings are defined.", + "Personal firewall (or equivalent functionality) is actively running.", + "Personal firewall (or equivalent functionality) is not alterable by users of the portable computing devices." + ] + } + ] + }, + "2.2": "Develop configuration standards for all system components. Assure that these standards address all known security vulnerabilities and are consistent with industry accepted system hardening standards (CIS, ISO, SANS, NIST).", + "2.2.2": "Enable only necessary services, protocols, daemons, etc., as required for the function of the system. ", + "2.2.3": "Implement additional security features for any required services, protocols, or daemons that are considered to be insecure", + "2.2.4": "Configure system security parameters to prevent misuse.", + "4.1": { + stack: [ + "Use strong cryptography and security protocols (for example, SSL/TLS, IPSEC, SSH, etc.) to safeguard sensitive cardholder data during transmission over open, public networks, including the following:", + {text:'\n'}, + { + ul: [ + "Only trusted keys and certificates are accepted.", + "The protocol in use only supports secure versions or configurations.", + "The encryption strength is appropriate for the encryption methodology in use." + ] + } + ] + }, + "5.1": "Deploy anti-virus software on all systems commonly affected by malicious software (particularly personal computers and servers).", + "5.2": { + stack: [ + "Ensure that all anti-virus mechanisms are maintained as follows:", + {text:'\n'}, + { + ul: [ + "Are kept current.", + "Perform periodic scans.", + "Generate audit logs which are retained per PCI DSS Requirement 10.7." + ] + } + ] + }, + "6.2": "Ensure that all system components and software are protected from known vulnerabilities by installing applicable vendor-supplied security patches. Install critical security patches within one month of release.", + "6.5": { + stack: [ + "Address common coding vulnerabilities in software development processes as follows:", + {text:'\n'}, + { + ul: [ + "Train developers in secure coding techniques, including how to avoid common coding vulnerabilities, and understanding how sensitive data is handled in memory.", + "Develop applications based on secure coding guidelines." + ] + } + ] + }, + "6.5.1": "Injection flaws, particularly SQL injection. Also consider OS Command Injection, LDAP and XPath injection flaws as well as other injection flaws.", + "6.5.2": "Buffer overflows", + "6.5.5": "Improper error handling", + "6.5.7": "Cross-site scripting (XSS)", + "6.5.8": "Improper access control (such an insecure direct object references, failure to restrict URL access, directory traversal, and failure to restrict user access to functions).", + "6.5.10": "Broken authentication and session management.", + "6.6": { + stack: [ + "For public-facing web applications, address new threats and vulnerabilities on an ongoing basis and ensure these applications are protected against known attacks by either of the following methods:", + {text:'\n'}, + { + ul: [ + "Reviewing public-facing web applications via manual or automated application vulnerability security assessment tools or methods, at least annually and after any changes", + "Installing an automated technical solution that detects and prevents web-based attacks (for example, a web-application firewall) in front of public-facing web applications, to continually check all traffic." + ] + } + ] + }, + "8.1.2": "Control addition, deletion, and modification of user IDs, credentials, and other identifier objects.", + "8.1.4": "Remove/disable inactive user accounts within 90 days.", + "8.1.5": + { + stack: [ + "Manage IDs used by third parties to access, support, or maintain system components via remote access as follows:", + {text:'\n'}, + { + ul: [ + "Enabled only during the time period needed and disabled when not in use.", + "Monitored when in use." + ] + } + ] + }, + "8.1.6": "Limit repeated access attempts by locking out the user ID after not more than six attempts.", + "8.1.8": "If a session has been idle for more than 15 minutes, require the user to reauthenticate to re-activate the terminal or session.", + "8.2.4": "Change user passwords/passphrases at least once every 90 days.", + "8.5.1": "Additional requirement for service providers: Service providers with remote access to customer premises (for example, for support of POS systems or servers) must use a unique authentication credential (such as a password/phrase) for each customer.", + "8.7": { + stack: [ + "All access to any database containing cardholder data (including access by applications, administrators, and all other users) is restricted as follows:", + {text:'\n'}, + { + ul: [ + "All user access to, user queries of, and user actions on databases are through programmatic methods.", + "Only database administrators have the ability to directly access or query databases.", + "Application IDs for database applications can only be used by the applications (and not by individual users or other non-application processes)." + ] + } + ] + }, + "10.1": "Implement audit trails to link all access to system components to each individual user.", + "10.2.1": "All individual user accesses to cardholder data", + "10.2.2": "All actions taken by any individual with root or administrative privileges.", + "10.2.4": "Invalid logical access attempts", + "10.2.5": "Use of and changes to identification and authentication mechanisms including but not limited to creation of new accounts and elevation of privileges and all changes, additions, or deletions to accounts with root or administrative privileges.", + "10.2.6": "Initialization, stopping, or pausing of the audit logs", + "10.2.7": "Creation and deletion of system level objects", + "10.5.2": "Protect audit trail files from unauthorized modifications", + "10.5.5": "Use file integrity monitoring or change detection software on logs to ensure that existing log data cannot be changed without generating alerts (although new data being added should not cause an alert).", + "10.4": "Using time-synchronization technology, synchronize all critical system clocks and times and ensure that the following is implemented for acquiring, distributing, and storing time.", + "10.6": "Review logs and security events for all system components to identify anomalies or suspicious activity", + "10.6.1": { + stack: [ + "Review the following at least daily:", + {text:'\n'}, + { + ul: [ + "All security events.", + "Logs of all system components that store, process, or transmit CHD and/or SAD, or that could impact the security of CHD and/or SAD.", + "Logs of all critical system components.", + "Logs of all servers and system components that perform security functions (for example, firewalls, intrusion detection systems/intrusion prevention systems (IDS/IPS), authentication servers, ecommerce redirection servers, etc.)" + ] + } + ], + style: 'standard' + }, + "11.4": "Use intrusion detection and/or intrusion prevention techniques to detect and/or prevent intrusions into the network. Monitor all traffic at the perimeter of the cardholder data environment as well as at critical points in the cardholder data environment, and alert personnel to suspected compromises. Keep all intrusion detection and prevention engines, baselines, and signatures up to date.", + "11.5": "Deploy a change detection mechanism (for example, file integrity monitoring tools) to alert personnel to unauthorized modification of critical system files, configuration files, or content files; and configure the software to perform critical file comparisons at least weekly." +}; \ No newline at end of file diff --git a/server/integration-files/pci-requirements.js b/server/integration-files/pci-requirements.js index 2c9cc093a3..d2cb5b8718 100644 --- a/server/integration-files/pci-requirements.js +++ b/server/integration-files/pci-requirements.js @@ -51,4 +51,4 @@ export default { "10.6.1": "Review the following at least daily:
", "11.4": "Use intrusion detection and/or intrusion prevention techniques to detect and/or prevent intrusions into the network.
Monitor all traffic at the perimeter of the cardholder data environment as well as at critical points in the cardholder data environment, and alert personnel to suspected compromises. Keep all intrusion detection and prevention engines, baselines, and signatures up to date.", "11.5": "Deploy a change detection mechanism (for example, file integrity monitoring tools) to alert personnel to unauthorized modification of critical system files, configuration files, or content files; and configure the software to perform critical file comparisons at least weekly." -} +}; \ No newline at end of file diff --git a/server/reporting/audit-map.js b/server/reporting/audit-map.js new file mode 100644 index 0000000000..153613186a --- /dev/null +++ b/server/reporting/audit-map.js @@ -0,0 +1,346 @@ +/* + * Wazuh app - Most common Linux system calls + * Copyright (C) 2018 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 default { + "0": "read" + , "1": "write" + , "2": "open" + , "3": "close" + , "4": "stat" + , "5": "fstat" + , "6": "lstat" + , "7": "poll" + , "8": "lseek" + , "9": "mmap" + , "10": "mprotect" + , "11": "munmap" + , "12": "brk" + , "13": "rt_sigaction" + , "14": "rt_sigprocmask" + , "15": "rt_sigreturn" + , "16": "ioctl" + , "17": "pread64" + , "18": "pwrite64" + , "19": "readv" + , "20": "writev" + , "21": "access" + , "22": "pipe" + , "23": "select" + , "24": "sched_yield" + , "25": "mremap" + , "26": "msync" + , "27": "mincore" + , "28": "madvise" + , "29": "shmget" + , "30": "shmat" + , "31": "shmctl" + , "32": "dup" + , "33": "dup2" + , "34": "pause" + , "35": "nanosleep" + , "36": "getitimer" + , "37": "alarm" + , "38": "setitimer" + , "39": "getpid" + , "40": "sendfile" + , "41": "socket" + , "42": "connect" + , "43": "accept" + , "44": "sendto" + , "45": "recvfrom" + , "46": "sendmsg" + , "47": "recvmsg" + , "48": "shutdown" + , "49": "bind" + , "50": "listen" + , "51": "getsockname" + , "52": "getpeername" + , "53": "socketpair" + , "54": "setsockopt" + , "55": "getsockopt" + , "56": "clone" + , "57": "fork" + , "58": "vfork" + , "59": "execve" + , "60": "exit" + , "61": "wait4" + , "62": "kill" + , "63": "uname" + , "64": "semget" + , "65": "semop" + , "66": "semctl" + , "67": "shmdt" + , "68": "msgget" + , "69": "msgsnd" + , "70": "msgrcv" + , "71": "msgctl" + , "72": "fcntl" + , "73": "flock" + , "74": "fsync" + , "75": "fdatasync" + , "76": "truncate" + , "77": "ftruncate" + , "78": "getdents" + , "79": "getcwd" + , "80": "chdir" + , "81": "fchdir" + , "82": "rename" + , "83": "mkdir" + , "84": "rmdir" + , "85": "creat" + , "86": "link" + , "87": "unlink" + , "88": "symlink" + , "89": "readlink" + , "90": "chmod" + , "91": "fchmod" + , "92": "chown" + , "93": "fchown" + , "94": "lchown" + , "95": "umask" + , "96": "gettimeofday" + , "97": "getrlimit" + , "98": "getrusage" + , "99": "sysinfo" + , "100": "times" + , "101": "ptrace" + , "102": "getuid" + , "103": "syslog" + , "104": "getgid" + , "105": "setuid" + , "106": "setgid" + , "107": "geteuid" + , "108": "getegid" + , "109": "setpgid" + , "110": "getppid" + , "111": "getpgrp" + , "112": "setsid" + , "113": "setreuid" + , "114": "setregid" + , "115": "getgroups" + , "116": "setgroups" + , "117": "setresuid" + , "118": "getresuid" + , "119": "setresgid" + , "120": "getresgid" + , "121": "getpgid" + , "122": "setfsuid" + , "123": "setfsgid" + , "124": "getsid" + , "125": "capget" + , "126": "capset" + , "127": "rt_sigpending" + , "128": "rt_sigtimedwait" + , "129": "rt_sigqueueinfo" + , "130": "rt_sigsuspend" + , "131": "sigaltstack" + , "132": "utime" + , "133": "mknod" + , "134": "uselib" + , "135": "personality" + , "136": "ustat" + , "137": "statfs" + , "138": "fstatfs" + , "139": "sysfs" + , "140": "getpriority" + , "141": "setpriority" + , "142": "sched_setparam" + , "143": "sched_getparam" + , "144": "sched_setscheduler" + , "145": "sched_getscheduler" + , "146": "sched_get_priority_max" + , "147": "sched_get_priority_min" + , "148": "sched_rr_get_interval" + , "149": "mlock" + , "150": "munlock" + , "151": "mlockall" + , "152": "munlockall" + , "153": "vhangup" + , "154": "modify_ldt" + , "155": "pivot_root" + , "156": "_sysctl" + , "157": "prctl" + , "158": "arch_prctl" + , "159": "adjtimex" + , "160": "setrlimit" + , "161": "chroot" + , "162": "sync" + , "163": "acct" + , "164": "settimeofday" + , "165": "mount" + , "166": "umount2" + , "167": "swapon" + , "168": "swapoff" + , "169": "reboot" + , "170": "sethostname" + , "171": "setdomainname" + , "172": "iopl" + , "173": "ioperm" + , "174": "create_module" + , "175": "init_module" + , "176": "delete_module" + , "177": "get_kernel_syms" + , "178": "query_module" + , "179": "quotactl" + , "180": "nfsservctl" + , "181": "getpmsg" + , "182": "putpmsg" + , "183": "afs_syscall" + , "184": "tuxcall" + , "185": "security" + , "186": "gettid" + , "187": "readahead" + , "188": "setxattr" + , "189": "lsetxattr" + , "190": "fsetxattr" + , "191": "getxattr" + , "192": "lgetxattr" + , "193": "fgetxattr" + , "194": "listxattr" + , "195": "llistxattr" + , "196": "flistxattr" + , "197": "removexattr" + , "198": "lremovexattr" + , "199": "fremovexattr" + , "200": "tkill" + , "201": "time" + , "202": "futex" + , "203": "sched_setaffinity" + , "204": "sched_getaffinity" + , "205": "set_thread_area" + , "206": "io_setup" + , "207": "io_destroy" + , "208": "io_getevents" + , "209": "io_submit" + , "210": "io_cancel" + , "211": "get_thread_area" + , "212": "lookup_dcookie" + , "213": "epoll_create" + , "214": "epoll_ctl_old" + , "215": "epoll_wait_old" + , "216": "remap_file_pages" + , "217": "getdents64" + , "218": "set_tid_address" + , "219": "restart_syscall" + , "220": "semtimedop" + , "221": "fadvise64" + , "222": "timer_create" + , "223": "timer_settime" + , "224": "timer_gettime" + , "225": "timer_getoverrun" + , "226": "timer_delete" + , "227": "clock_settime" + , "228": "clock_gettime" + , "229": "clock_getres" + , "230": "clock_nanosleep" + , "231": "exit_group" + , "232": "epoll_wait" + , "233": "epoll_ctl" + , "234": "tgkill" + , "235": "utimes" + , "236": "vserver" + , "237": "mbind" + , "238": "set_mempolicy" + , "239": "get_mempolicy" + , "240": "mq_open" + , "241": "mq_unlink" + , "242": "mq_timedsend" + , "243": "mq_timedreceive" + , "244": "mq_notify" + , "245": "mq_getsetattr" + , "246": "kexec_load" + , "247": "waitid" + , "248": "add_key" + , "249": "request_key" + , "250": "keyctl" + , "251": "ioprio_set" + , "252": "ioprio_get" + , "253": "inotify_init" + , "254": "inotify_add_watch" + , "255": "inotify_rm_watch" + , "256": "migrate_pages" + , "257": "openat" + , "258": "mkdirat" + , "259": "mknodat" + , "260": "fchownat" + , "261": "futimesat" + , "262": "newfstatat" + , "263": "unlinkat" + , "264": "renameat" + , "265": "linkat" + , "266": "symlinkat" + , "267": "readlinkat" + , "268": "fchmodat" + , "269": "faccessat" + , "270": "pselect6" + , "271": "ppoll" + , "272": "unshare" + , "273": "set_robust_list" + , "274": "get_robust_list" + , "275": "splice" + , "276": "tee" + , "277": "sync_file_range" + , "278": "vmsplice" + , "279": "move_pages" + , "280": "utimensat" + , "281": "epoll_pwait" + , "282": "signalfd" + , "283": "timerfd_create" + , "284": "eventfd" + , "285": "fallocate" + , "286": "timerfd_settime" + , "287": "timerfd_gettime" + , "288": "accept4" + , "289": "signalfd4" + , "290": "eventfd2" + , "291": "epoll_create1" + , "292": "dup3" + , "293": "pipe2" + , "294": "inotify_init1" + , "295": "preadv" + , "296": "pwritev" + , "297": "rt_tgsigqueueinfo" + , "298": "perf_event_open" + , "299": "recvmmsg" + , "300": "fanotify_init" + , "301": "fanotify_mark" + , "302": "prlimit64" + , "303": "name_to_handle_at" + , "304": "open_by_handle_at" + , "305": "clock_adjtime" + , "306": "syncfs" + , "307": "sendmmsg" + , "308": "setns" + , "309": "getcpu" + , "310": "process_vm_readv" + , "311": "process_vm_writev" + , "312": "kcmp" + , "313": "finit_module" + , "314": "sched_setattr" + , "315": "sched_getattr" + , "316": "renameat2" + , "317": "seccomp" + , "318": "getrandom" + , "319": "memfd_create" + , "320": "kexec_file_load" + , "321": "bpf" + , "322": "execveat" + , "323": "userfaultfd" + , "324": "membarrier" + , "325": "mlock2" + , "326": "copy_file_range" + , "327": "preadv2" + , "328": "pwritev2" + , "329": "pkey_mprotect" + , "330": "pkey_alloc" + , "331": "pkey_free" + , "332": "statx" +}; \ No newline at end of file diff --git a/server/reporting/audit-request.js b/server/reporting/audit-request.js new file mode 100644 index 0000000000..866df02796 --- /dev/null +++ b/server/reporting/audit-request.js @@ -0,0 +1,185 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh Audit data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; +import AuditMap from './audit-map'; + +export default class PciRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 3 agents that execute sudo commands without success + * @param {*} gte + * @param {*} lte + * @param {*} filters + * @param {*} pattern + */ + async getTop3AgentsSudoNonSuccessful(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base,Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "3": { + "terms": { + "field": "agent.id", + "size": 3, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.audit.uid": { + "query": "0" + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.audit.success": { + "query": "no" + } + } + }); + + base.query.bool.must_not.push({ + "match_phrase": { + "data.audit.auid": { + "query": "0" + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['3']; + return buckets.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns the most failed syscall in the top 3 agents with failed system calls + * @param {*} gte + * @param {*} lte + * @param {*} filters + * @param {*} pattern + */ + async getTop3AgentsFailedSyscalls(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base,Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "3": { + "terms": { + "field": "agent.id", + "size": 3, + "order": { + "_count": "desc" + } + }, + "aggs": { + "4": { + "terms": { + "field": "data.audit.syscall", + "size": 1, + "order": { + "_count": "desc" + } + } + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.audit.success": { + "query": "no" + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['3']; + + const result = []; + for (const bucket of buckets) { + try { + const agent = bucket.key; + const syscall = { id: bucket['4'].buckets[0].key, syscall: AuditMap[bucket['4'].buckets[0].key] || 'Warning: Unknown system call' }; + result.push({ + agent, syscall + }); + } catch (error) { + continue; + } + + } + return result; + + } catch (error) { + return Promise.reject(error); + } + } + + async getTopFailedSyscalls(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base,Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "data.audit.syscall", + "size": 10, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.audit.success": { + "query": "no" + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets.map(item => { return { id:item.key, syscall:AuditMap[item.key] };}); + + } catch (error) { + return Promise.reject(error); + } + } +} \ No newline at end of file diff --git a/server/reporting/base-query.js b/server/reporting/base-query.js new file mode 100644 index 0000000000..94979572f8 --- /dev/null +++ b/server/reporting/base-query.js @@ -0,0 +1,57 @@ +/* + * Wazuh app - Base query for reporting queries + * Copyright (C) 2018 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 default (pattern, filters, gte, lte) => { + return { + "pattern": pattern, + "size": 0, + "aggs": { + + }, + "stored_fields": [ + "*" + ], + "script_fields": {}, + "docvalue_fields": [ + "@timestamp", + "data.vulnerability.published", + "data.vulnerability.updated", + "syscheck.mtime_after", + "syscheck.mtime_before", + "data.cis.timestamp" + ], + "query": { + "bool": { + "must": [ + { + "query_string": { + "query": filters, + "analyze_wildcard": true, + "default_field": "*" + } + }, + { + "range": { + "@timestamp": { + "gte": gte, + "lte": lte, + "format": "epoch_millis" + } + } + } + ], + "must_not": [ + + ] + } + } + }; +}; \ No newline at end of file diff --git a/server/reporting/clock-icon-raw.js b/server/reporting/clock-icon-raw.js new file mode 100644 index 0000000000..9f00858d2b --- /dev/null +++ b/server/reporting/clock-icon-raw.js @@ -0,0 +1 @@ +export default 'data:image/png;base64,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data:image/png;base64,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'; \ No newline at end of file diff --git a/server/reporting/clock.png b/server/reporting/clock.png deleted file mode 100644 index b7718ac49e..0000000000 Binary files a/server/reporting/clock.png and /dev/null differ diff --git a/server/reporting/filter-icon-raw.js b/server/reporting/filter-icon-raw.js new file mode 100644 index 0000000000..a868197140 --- /dev/null +++ b/server/reporting/filter-icon-raw.js @@ -0,0 +1 @@ +export default 'data:image/png;base64,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'; \ No newline at end of file diff --git a/server/reporting/filters.png b/server/reporting/filters.png deleted file mode 100644 index 6bccb2b5d5..0000000000 Binary files a/server/reporting/filters.png and /dev/null differ diff --git a/server/reporting/gdpr-request.js b/server/reporting/gdpr-request.js new file mode 100644 index 0000000000..d892b2c2d3 --- /dev/null +++ b/server/reporting/gdpr-request.js @@ -0,0 +1,134 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh GDPR data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class GdprRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 5 GDPR requirements + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async topGDPRRequirements(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + if (filters.includes('rule.gdpr: exists')) { + const first = filters.split('AND rule.gdpr: exists')[0]; + const second = filters.split('AND rule.gdpr: exists')[1]; + filters = first + second; + } + + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "rule.gdpr", + "size": 5, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "exists": { + "field": "rule.gdpr" + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + + return aggArray.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns top 3 rules for specific GDPR requirement + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} requirement GDPR requirement. E.g: 'II_5.1.F' + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async getRulesByRequirement(gte, lte, filters, requirement, pattern = 'wazuh-alerts-3.x-*') { + if (filters.includes('rule.gdpr: exists')) { + const first = filters.split('AND rule.gdpr: exists')[0]; + const second = filters.split('AND rule.gdpr: exists')[1]; + filters = first + second; + } + + try { + + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "rule.description", + "size": 3, + "order": { + "_count": "desc" + } + }, + "aggs": { + "3": { + "terms": { + "field": "rule.id", + "size": 1, + "order": { + "_count": "desc" + } + } + } + } + } + }); + + base.query.bool.must[0].query_string.query = base.query.bool.must[0].query_string.query + " AND rule.gdpr: \"" + requirement + "\""; + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + const result = []; + for (const bucket of buckets) { + if(!bucket || !bucket['3'] || !bucket['3'].buckets || !bucket['3'].buckets[0] || !bucket['3'].buckets[0].key || !bucket.key){ + continue; + } + const ruleId = bucket['3'].buckets[0].key; + const ruleDescription = bucket.key; + result.push({ ruleId, ruleDescription }); + } + + return result; + } catch (error) { + return Promise.reject(error); + } + } +} \ No newline at end of file diff --git a/server/reporting/generic-table.js b/server/reporting/generic-table.js new file mode 100644 index 0000000000..ce2affd70d --- /dev/null +++ b/server/reporting/generic-table.js @@ -0,0 +1,97 @@ +/* + * Wazuh app - Base table for reporting tables + * Copyright (C) 2018 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. + */ + +/** + * Useful function to build tables using pdfmake + * @param {*} document Document provided by pdfmake module + * @param {Array<*>} items List of items (rows) + * @param {Array} keys Properties from each item + * @param {Array} columns Columns for the table. E.g: ['Col1','Col2','Col3'] + * @param {String} title Optional. Title for the table + */ +export default (document, items, columns, keys, title, givenRows = false) => { + + if(!document || !columns || !columns.length) { + throw new Error('Missing parameters when building table'); + } + + if(title) { + document.content.push({ text: title, style: 'h4' }); + document.content.push({text:'\n'}) + } + + if(!items || !items.length) { + document.content.push({ text: 'No results match your search criteria', style:'standard'}); + return; + } + + const rowSize = givenRows ? items[0].length : keys.length; + const rows = givenRows ? items : []; + const modifiedRows = []; + if(!givenRows){ + for(const item of items){ + const str = new Array(rowSize).fill('---'); + for(let i=0; i { + return {text:cell,style:'standard'}; + })); + } + } else { + + for(const row of rows){ + modifiedRows.push(row.map(cell => { + return {text:cell,style:'standard'}; + })); + } + } + + const fullBody = []; + + const widths = new Array(rowSize-1).fill('auto'); + widths.push('*'); + fullBody.push( + columns.map(col => { + return {text:col,style:'whiteColor',border:[0,0,0,0]}; + }) + ); + + if(givenRows){ + fullBody.push(...modifiedRows); + } else { + fullBody.push(...rows); + } + + document.content.push({ + fontSize:8, + table: { + headerRows:1, + widths, + body: fullBody + }, + layout: { + fillColor: i => i === 0 ? '#78C8DE' : null, + hLineColor: () => '#78C8DE', + hLineWidth: () => 1, + vLineWidth: () => 0 + } + }); +}; \ No newline at end of file diff --git a/server/reporting/overview-request.js b/server/reporting/overview-request.js new file mode 100644 index 0000000000..68335aabae --- /dev/null +++ b/server/reporting/overview-request.js @@ -0,0 +1,67 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh overview data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class VulnerabilityRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 3 agents with level 15 alerts + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} E.g:['000','130','300'] + */ + async topLevel15(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "agent.id", + "size": 3, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "rule.level": { + "query": 15 + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + + return aggArray.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + +} \ No newline at end of file diff --git a/server/reporting/pci-request.js b/server/reporting/pci-request.js new file mode 100644 index 0000000000..a56bf040e7 --- /dev/null +++ b/server/reporting/pci-request.js @@ -0,0 +1,148 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh PCI DSS data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class PciRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 5 PCI DSS requirements + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async topPCIRequirements(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + if (filters.includes('rule.pci_dss: exists')) { + const first = filters.split('AND rule.pci_dss: exists')[0]; + const second = filters.split('AND rule.pci_dss: exists')[1]; + filters = first + second; + } + + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "rule.pci_dss", + "size": 5, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "exists": { + "field": "rule.pci_dss" + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + + return aggArray.map(item => item.key).sort((a, b) => { + const a_split = a.split('.'); + const b_split = b.split('.'); + if (parseInt(a_split[0]) > parseInt(b_split[0])) return 1; + else if (parseInt(a_split[0]) < parseInt(b_split[0])) return -1; + else { + if (parseInt(a_split[1]) > parseInt(b_split[1])) return 1; + else if (parseInt(a_split[1]) < parseInt(b_split[1])) return -1; + else { + if (parseInt(a_split[2]) > parseInt(b_split[2])) return 1; + else if (parseInt(a_split[2]) < parseInt(b_split[2])) return -1; + } + } + }); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns top 3 rules for specific PCI DSS requirement + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} requirement PCI DSS requirement. E.g: '10.2.3' + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async getRulesByRequirement(gte, lte, filters, requirement, pattern = 'wazuh-alerts-3.x-*') { + if (filters.includes('rule.pci_dss: exists')) { + const first = filters.split('AND rule.pci_dss: exists')[0]; + const second = filters.split('AND rule.pci_dss: exists')[1]; + filters = first + second; + } + + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "rule.description", + "size": 3, + "order": { + "_count": "desc" + } + }, + "aggs": { + "3": { + "terms": { + "field": "rule.id", + "size": 1, + "order": { + "_count": "desc" + } + } + } + } + } + }); + + base.query.bool.must[0].query_string.query = base.query.bool.must[0].query_string.query + " AND rule.pci_dss: \"" + requirement + "\""; + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + const result = []; + for (const bucket of buckets) { + if (!bucket || !bucket['3'] || !bucket['3'].buckets || !bucket['3'].buckets[0] || !bucket['3'].buckets[0].key || !bucket.key) { + continue; + } + const ruleId = bucket['3'].buckets[0].key; + const ruleDescription = bucket.key; + result.push({ ruleId, ruleDescription }); + } + + return result; + + } catch (error) { + return Promise.reject(error); + } + } +} \ No newline at end of file diff --git a/server/reporting/rootcheck-request.js b/server/reporting/rootcheck-request.js new file mode 100644 index 0000000000..0afc9c99c8 --- /dev/null +++ b/server/reporting/rootcheck-request.js @@ -0,0 +1,146 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh rootcheck data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class RootcheckRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 5 rootkits found along all agents + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async top5RootkitsDetected(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*',size = 5) { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "data.title", + "size": size, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must[0].query_string.query = base.query.bool.must[0].query_string.query + " AND \"rootkit\" AND \"detected\""; + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + const mapped = aggArray.map(item => item.key); + const result = []; + + for (const item of mapped) { + result.push(item.split("'")[1].split("'")[0]); + } + + return result.filter((item, pos) => result.indexOf(item) === pos); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns the number of agents that have one or more hidden processes + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async agentsWithHiddenPids(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "1": { + "cardinality": { + "field": "agent.id" + } + } + }); + + base.query.bool.must[0].query_string.query = base.query.bool.must[0].query_string.query + " AND \"process\" AND \"hidden\""; + + // "aggregations": { "1": { "value": 1 } } + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + + return (response && + response.aggregations && + response.aggregations['1'] && + response.aggregations['1'].value) ? + + response.aggregations['1'].value : + + 0; + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns the number of agents that have one or more hidden ports + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async agentsWithHiddenPorts(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "1": { + "cardinality": { + "field": "agent.id" + } + } + }); + + base.query.bool.must[0].query_string.query = base.query.bool.must[0].query_string.query + " AND \"port\" AND \"hidden\""; + + // "aggregations": { "1": { "value": 1 } } + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + + return (response && + response.aggregations && + response.aggregations['1'] && + response.aggregations['1'].value) ? + + response.aggregations['1'].value : + + 0; + + } catch (error) { + return Promise.reject(error); + } + } +} \ No newline at end of file diff --git a/server/reporting/syscheck-request.js b/server/reporting/syscheck-request.js new file mode 100644 index 0000000000..7b8e5c9f2a --- /dev/null +++ b/server/reporting/syscheck-request.js @@ -0,0 +1,217 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh syscheck data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class SyscheckRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 3 dangerous agents + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async top3agents(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "agent.id", + "size": 3, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "range": { + "rule.level": { + "gte": 7, + "lt": 16 + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns top 3 rules + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async top3Rules(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "rule.description", + "size": 3, + "order": { + "_count": "desc" + } + }, + "aggs": { + "3": { + "terms": { + "field": "rule.id", + "size": 1, + "order": { + "_count": "desc" + } + } + } + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + const result = []; + for (const bucket of buckets) { + if(!bucket || !bucket['3'] || !bucket['3'].buckets || !bucket['3'].buckets[0] || !bucket['3'].buckets[0].key || !bucket.key){ + continue; + } + const ruleId = bucket['3'].buckets[0].key; + const ruleDescription = bucket.key; + result.push({ ruleId, ruleDescription }); + } + + return result; + + } catch (error) { + return Promise.reject(error); + } + } + + async lastTenDeletedFiles(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "syscheck.path", + "size": 10, + "order": { + "1": "desc" + } + }, + "aggs": { + "1": { + "max": { + "field": "@timestamp" + } + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "syscheck.event": { + "query": "deleted" + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets + .map(item => { + return{ date: item['1'].value_as_string, path: item.key }; + }) + .sort((a,b) => (a.date > b.date) ? -1 : (a.date < b.date ? 1 : 0)); + + } catch (error) { + return Promise.reject(error); + } + } + + async lastTenModifiedFiles(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "syscheck.path", + "size": 10, + "order": { + "1": "desc" + } + }, + "aggs": { + "1": { + "max": { + "field": "@timestamp" + } + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "syscheck.event": { + "query": "modified" + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets + .map(item => { + return{ date: item['1'].value_as_string, path: item.key }; + }) + .sort((a,b) => (a.date > b.date) ? -1 : (a.date < b.date ? 1 : 0)); + + } catch (error) { + return Promise.reject(error); + } + } +} \ No newline at end of file diff --git a/server/reporting/tab-description.js b/server/reporting/tab-description.js index e526828ebd..da63bd10a9 100644 --- a/server/reporting/tab-description.js +++ b/server/reporting/tab-description.js @@ -53,5 +53,9 @@ export default { virustotal: { title: 'Virustotal', description:'From version 3.0.0, Wazuh incorporates a new integration which scans monitored files for malicious content. This solution is possible through an integration with VirusTotal, which is a powerful platform that aggregates multiple antivirus products along with an online scanning engine. Combining this tool with our FIM engine provides a simple means of scanning the files that are monitored by syscheck to inspect them for malicious content.' + }, + syscollector: { + title: 'Inventory', + description: 'Scan the system to retrieve OS, hardware and installed packages related information.' } } diff --git a/server/reporting/vulnerability-request.js b/server/reporting/vulnerability-request.js new file mode 100644 index 0000000000..e1ff80d041 --- /dev/null +++ b/server/reporting/vulnerability-request.js @@ -0,0 +1,237 @@ +/* + * Wazuh app - Specific methods to fetch Wazuh vulnerability data from Elasticsearch + * Copyright (C) 2018 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 ElasticWrapper from '../lib/elastic-wrapper'; +import Base from './base-query'; + +export default class VulnerabilityRequest { + /** + * Constructor + * @param {*} server Hapi.js server object provided by Kibana + */ + constructor(server) { + this.wzWrapper = new ElasticWrapper(server); + } + + /** + * Returns top 3 agents for specific severity + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} severity Low, Medium, High, Critical + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async topAgentCount(gte, lte, severity, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "agent.id", + "size": 3, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.vulnerability.severity": { + "query": severity + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + + return aggArray.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns top 3 CVE + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Array} + */ + async topCVECount(gte, lte, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs,{ + "2": { + "terms": { + "field": "data.vulnerability.cve", + "size": 3, + "order": { + "_count": "desc" + } + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const aggArray = response.aggregations['2'].buckets; + + return aggArray.map(item => item.key); + + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Returns unique count of vulnerability alerts using specific severity. + * @param {Number} gte Timestamp (ms) from + * @param {Number} lte Timestamp (ms) to + * @param {String} severity Low, Medium, High, Critical + * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability + * @returns {Number} + */ + async uniqueSeverityCount(gte, lte, severity, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "1": { + "cardinality": { + "field": "agent.id" + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.vulnerability.severity": { + "query": severity + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + + //aggregations: { '1': { value: 2 } } } + return response && + response.aggregations && + response.aggregations['1'] && + response.aggregations['1'].value ? + response.aggregations['1'].value : + 0; + + } catch (error) { + return Promise.reject(error); + } + } + + async topPackages(gte, lte, severity, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "data.vulnerability.package.name", + "size": 20, + "order": { + "_count": "desc" + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.vulnerability.severity": { + "query": severity + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets.map(item => {return {package: item.key, severity: severity};}); + + } catch (error) { + return Promise.reject(error); + } + } + + async topPackagesWithCVE(gte, lte, severity, filters, pattern = 'wazuh-alerts-3.x-*') { + try { + const base = {}; + + Object.assign(base, Base(pattern, filters, gte, lte)); + + Object.assign(base.aggs, { + "2": { + "terms": { + "field": "data.vulnerability.package.name", + "size": 3, + "order": { + "_count": "desc" + } + }, + "aggs": { + "3": { + "terms": { + "field": "data.vulnerability.reference", + "size": 10, + "order": { + "_count": "desc" + } + } + } + } + } + }); + + base.query.bool.must.push({ + "match_phrase": { + "data.vulnerability.severity": { + "query": severity + } + } + }); + + const response = await this.wzWrapper.searchWazuhAlertsWithPayload(base); + const { buckets } = response.aggregations['2']; + + return buckets.map(item => { + return { + package: item.key, + references: item['3'].buckets.map(ref => ref.key) + }; + }); + + } catch (error) { + return Promise.reject(error); + } + } + + +} \ No newline at end of file diff --git a/server/routes/wazuh-reporting.js b/server/routes/wazuh-reporting.js index 52de185d80..aa0a520e0a 100644 --- a/server/routes/wazuh-reporting.js +++ b/server/routes/wazuh-reporting.js @@ -12,7 +12,7 @@ import { WazuhReportingCtrl } from '../controllers'; export default (server, options) => { - const ctrl = new WazuhReportingCtrl(); + const ctrl = new WazuhReportingCtrl(server); // Builds a PDF report from multiple PNG images server.route({ method: 'POST', path: '/api/wazuh-reporting/report', handler: (req,res) => ctrl.report(req,res)});