diff --git a/CHANGELOG.md b/CHANGELOG.md index c6039db5f0..1f2c1dca61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Wazuh app project will be documented in this file. +## Wazuh v3.13.5 - Kibana 7.9.2 - Revision 889 + +- Sanitize report's inputs and usernames [#4336](https://github.com/wazuh/wazuh-kibana-app/pull/4336) + ## Wazuh v3.13.2 - Kibana v7.9.2 - Revision 887 ### Added diff --git a/README.md b/README.md index 2197d0f0e7..f208f00f23 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,9 @@ This plugin for Kibana allows you to visualize and analyze Wazuh alerts stored i ## Requisites -- Wazuh HIDS 3.13.2 -- Wazuh RESTful API 3.13.2 -- Kibana 7.9.1 +- Wazuh HIDS 3.13.5 +- Wazuh RESTful API 3.13.5 +- Kibana 7.9.3 - Elasticsearch 7.9.2 ## Installation @@ -98,7 +98,7 @@ Install the Wazuh app plugin for Kibana ``` cd /usr/share/kibana -sudo -u kibana bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.13.2_7.9.2.zip +sudo -u kibana bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.13.5_7.9.2.zip ``` Restart Kibana @@ -164,7 +164,7 @@ Install the Wazuh app ``` cd /usr/share/kibana/ -sudo -u kibana bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.13.2_7.9.2.zip +sudo -u kibana bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.13.5_7.9.2.zip ``` Update configuration file permissions. @@ -193,6 +193,9 @@ service kibana restart | Wazuh app | Kibana | Open Distro | Package | | :-------: | :----: | :---------: | :-------------------------------------------------------------- | +| 3.13.5 | 7.9.2 | | | +| 3.13.4 | 7.9.2 | | | +| 3.13.3 | 7.9.2 | | | | 3.13.2 | 7.9.2 | | | | 3.13.2 | 7.9.1 | | | | 3.13.2 | 7.8.0 | | | diff --git a/package.json b/package.json index d1f5da07f8..e3c30cb326 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "wazuh", - "version": "3.13.2", - "revision": "0886", - "code": "0886-0", + "version": "3.13.5", + "revision": "0889", + "code": "0889-0", "kibana": { "version": "7.9.2" }, @@ -37,7 +37,8 @@ "test": "_mocha test/**/*", "test:ui:runner": "node ../../scripts/functional_test_runner.js", "test:server": "plugin-helpers test:server", - "test:browser": "plugin-helpers test:browser" + "test:browser": "plugin-helpers test:browser", + "test:jest": "node test/jest/jest" }, "dependencies": { "angular-animate": "1.7.8", diff --git a/public/react-services/reporting.js b/public/react-services/reporting.js index f594a244db..bbeb453ccf 100644 --- a/public/react-services/reporting.js +++ b/public/react-services/reporting.js @@ -93,16 +93,11 @@ export class ReportingService { ); const array = await this.vis2png.checkArray(idArray); - const name = `wazuh-${ - isAgents ? 'agents' : 'overview' - }-${tab}-${(Date.now() / 1000) | 0}.pdf`; const browserTimezone = moment.tz.guess(true); const data = { array, - name, - title: isAgents ? `Agents ${tab}` : `Overview ${tab}`, filters: appliedFilters.filters, time: appliedFilters.time, searchBar: appliedFilters.searchBar, @@ -138,26 +133,17 @@ export class ReportingService { this.$rootScope.reportStatus = 'Generating PDF document...'; this.$rootScope.$applyAsync(); - const docType = - type === 'agentConfig' - ? `wazuh-agent-${obj.id}` - : `wazuh-group-${obj.name}`; - - const name = `${docType}-configuration-${(Date.now() / 1000) | 0}.pdf`; const browserTimezone = moment.tz.guess(true); const data = { array: [], - name, - filters: [ - type === 'agentConfig' ? { agent: obj.id } : { group: obj.name } - ], time: '', searchBar: '', tables: [], tab: type, browserTimezone, - components + components, + ...(type === 'agentConfig' ? { agentID: obj.id } : { groupID: obj.name }) }; await this.genericReq.request('POST', '/reports', data); diff --git a/public/services/reporting.js b/public/services/reporting.js index 64dd91350f..be402ffefe 100644 --- a/public/services/reporting.js +++ b/public/services/reporting.js @@ -85,16 +85,11 @@ export class ReportingService { ); const array = await this.vis2png.checkArray(idArray); - const name = `wazuh-${ - isAgents ? 'agents' : 'overview' - }-${tab}-${(Date.now() / 1000) | 0}.pdf`; const browserTimezone = moment.tz.guess(true); const data = { array, - name, - title: isAgents ? `Agents ${tab}` : `Overview ${tab}`, filters: appliedFilters.filters, time: appliedFilters.time, searchBar: appliedFilters.searchBar, @@ -129,26 +124,17 @@ export class ReportingService { this.$rootScope.reportStatus = 'Generating PDF document...'; this.$rootScope.$applyAsync(); - const docType = - type === 'agentConfig' - ? `wazuh-agent-${obj.id}` - : `wazuh-group-${obj.name}`; - - const name = `${docType}-configuration-${(Date.now() / 1000) | 0}.pdf`; const browserTimezone = moment.tz.guess(true); const data = { array: [], - name, - filters: [ - type === 'agentConfig' ? { agent: obj.id } : { group: obj.name } - ], time: '', searchBar: '', tables: [], tab: type, browserTimezone, - components + components, + ...(type === 'agentConfig' ? { agentID: obj.id } : { groupID: obj.name }) }; await this.genericReq.request('POST', '/reports', data); diff --git a/server/controllers/wazuh-reporting.js b/server/controllers/wazuh-reporting.js index 4f1ae6935c..486b657150 100644 --- a/server/controllers/wazuh-reporting.js +++ b/server/controllers/wazuh-reporting.js @@ -1862,6 +1862,7 @@ export class WazuhReportingCtrl { * @returns {Object} pdf or ErrorResponse */ async report(req, reply) { + let pathFilename; try { log('reporting:report', `Report started`, 'info'); // Init @@ -1886,7 +1887,7 @@ export class WazuhReportingCtrl { if (req.payload && req.payload.array) { const payload = (req || {}).payload || {}; const headers = (req || {}).headers || {}; - const { name, tab, section, isAgents, browserTimezone } = payload; + const { tab, section, isAgents, browserTimezone, agentID, groupID } = payload; const apiId = headers.id || false; const pattern = headers.pattern || false; const from = (payload.time || {}).from || false; @@ -1895,6 +1896,20 @@ export class WazuhReportingCtrl { const isAgentConfig = tab === 'agentConfig'; const isGroupConfig = tab === 'groupConfig'; + // Generate the filename of report depeding on request parameters + const filename = tab === 'syscollector' + ? `wazuh-agent-inventory-${isAgents}-${this.generateReportTimestamp()}.pdf` + : (isAgentConfig + ? `wazuh-agent-configuration-${agentID}-${this.generateReportTimestamp()}.pdf` + : ( isGroupConfig + ? `wazuh-group-configuration-${groupID}-${this.generateReportTimestamp()}.pdf` + : `wazuh-module-${isAgents ? `agents-${isAgents}` : 'overview'}-${tab}-${this.generateReportTimestamp()}.pdf` + ) + ); + + // Generate the path to filename + pathFilename = path.join(__dirname, REPORTING_PATH, filename); + // Pass the namespace if present to all the requesters if (pattern) { const spaces = this.server.plugins.spaces; @@ -1921,10 +1936,6 @@ export class WazuhReportingCtrl { 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' - ); let tables = []; if (isGroupConfig) { @@ -1940,11 +1951,10 @@ export class WazuhReportingCtrl { labels: 'Labels', sca: 'Security configuration assessment' }; - const g_id = kfilters[0].group; kfilters = []; const enabledComponents = req.payload.components; this.dd.content.push({ - text: `Group ${g_id} configuration`, + text: `Group ${groupID} configuration`, style: 'h1' }); if (enabledComponents['0']) { @@ -1952,7 +1962,7 @@ export class WazuhReportingCtrl { try { configuration = await this.apiRequest.makeGenericRequest( 'GET', - `/agents/groups/${g_id}/configuration`, + `/agents/groups/${groupID}/configuration`, {}, apiId ); @@ -2150,7 +2160,7 @@ export class WazuhReportingCtrl { try { agentsInGroup = await this.apiRequest.makeGenericRequest( 'GET', - `/agents/groups/${g_id}`, + `/agents/groups/${groupID}`, {}, apiId ); @@ -2159,7 +2169,7 @@ export class WazuhReportingCtrl { } await this.renderHeader( tab, - g_id, + groupID, (((agentsInGroup || []).data || []).items || []).map(x => x.id), apiId ); @@ -2168,12 +2178,11 @@ export class WazuhReportingCtrl { if (isAgentConfig) { const configurations = AgentConfiguration.configurations; const enabledComponents = req.payload.components; - const a_id = kfilters[0].agent; let wmodules = {}; try { wmodules = await this.apiRequest.makeGenericRequest( 'GET', - `/agents/${a_id}/config/wmodules/wmodules`, + `/agents/${agentID}/config/wmodules/wmodules`, {}, apiId ); @@ -2182,7 +2191,7 @@ export class WazuhReportingCtrl { } kfilters = []; - await this.renderHeader(tab, tab, a_id, apiId); + await this.renderHeader(tab, tab, agentID, apiId); let idxComponent = 0; for (let config of configurations) { let titleOfSection = false; @@ -2211,7 +2220,7 @@ export class WazuhReportingCtrl { if (!conf['name']) { data = await this.apiRequest.makeGenericRequest( 'GET', - `/agents/${a_id}/config/${conf.component}/${conf.configuration}`, + `/agents/${agentID}/config/${conf.component}/${conf.configuration}`, {}, apiId ); @@ -2692,9 +2701,7 @@ export class WazuhReportingCtrl { const pdfDoc = this.printer.createPdfKitDocument(this.dd); await pdfDoc.pipe( - fs.createWriteStream( - path.join(__dirname, REPORTING_PATH + '/' + req.payload.name) - ) + fs.createWriteStream(pathFilename) ); pdfDoc.end(); } @@ -2702,15 +2709,8 @@ export class WazuhReportingCtrl { } catch (error) { log('reporting:report', error.message || error); // Delete generated file if an error occurred - if ( - ((req || {}).payload || {}).name && - fs.existsSync( - path.join(__dirname, REPORTING_PATH + '/' + req.payload.name) - ) - ) { - fs.unlinkSync( - path.join(__dirname, REPORTING_PATH + '/' + req.payload.name) - ); + if ( pathFilename && fs.existsSync(pathFilename) ) { + fs.unlinkSync(pathFilename); } return ErrorResponse(error.message || error, 5029, 500, reply); } @@ -2796,4 +2796,12 @@ export class WazuhReportingCtrl { return ErrorResponse(error.message || error, 5032, 500, reply); } } + + /** + * Generate a current timestamp in seconds + * @returns + */ + generateReportTimestamp(){ + return `${(Date.now() / 1000) | 0}`; + } } diff --git a/server/routes/wazuh-reporting-enpoint-parameters-validation.test.ts b/server/routes/wazuh-reporting-enpoint-parameters-validation.test.ts new file mode 100644 index 0000000000..513a67c362 --- /dev/null +++ b/server/routes/wazuh-reporting-enpoint-parameters-validation.test.ts @@ -0,0 +1,249 @@ +import { Router } from '../../../../src/core/server/http/router/router'; +import { HttpServer } from '../../../../src/core/server/http/http_server'; +import { loggingSystemMock } from '../../../../src/core/server/logging/logging_system.mock'; +import { ByteSizeValue } from '@kbn/config-schema'; +import supertest from 'supertest'; +import { WazuhReportingRoutes } from './wazuh-reporting'; +import md5 from 'md5'; + +jest.mock('pdfmake/src/printer', () => jest.fn(function () { })); +jest.mock('../reporting/vulnerability-request', () => ({ + VulnerabilityRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/overview-request', () => ({ + OverviewRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/rootcheck-request', () => ({ + RootcheckRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/pci-request', () => ({ + PciRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/overview-request', () => ({ + OverviewRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/gdpr-request', () => ({ + GdprRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/tsc-request', () => ({ + TSCRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/audit-request', () => ({ + AuditRequest: jest.fn(function () { }) +})); +jest.mock('../reporting/syscheck-request', () => ({ + SyscheckRequest: jest.fn(function () { }) +})); +jest.mock('../controllers/wazuh-api', () => ({ + WazuhApiCtrl: jest.fn(function () { }) +})); + +const loggingService = loggingSystemMock.create(); +const logger = loggingService.get(); +const context = { + wazuh: { + security: { + getCurrentUser: (request) => { + // x-test-username header doesn't exist when the platform or plugin are running. + // It is used to generate the output of this method so we can simulate the user + // that does the request to the endpoint and is expected by the endpoint handlers + // of the plugin. + const username = request.headers['x-test-username']; + return { username, hashUsername: md5(username) } + } + } + } +}; +const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context); +let server, innerServer; + +beforeAll(async () => { + // Create server + const config = { + name: 'plugin_platform', + host: '127.0.0.1', + maxPayload: new ByteSizeValue(1024), + port: 10002, + ssl: { enabled: false }, + compression: { enabled: true }, + requestId: { + allowFromAnyIp: true, + ipAllowlist: [], + }, + } as any; + server = new HttpServer(loggingService, 'tests'); + const router = new Router('', logger, enhanceWithContext); + const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config); + innerServer = innerServerTest; + + // Register routes + WazuhReportingRoutes(server.server); + + // start server + await server.start(); +}); + +afterAll(async () => { + // Stop server + await server.stop(); +}); + +describe('[security] GET /reports/{name}', () => { + it.each` + titleTest | name | responseStatusCode | responseBodyMessage + ${'Route not found'} |${'../wazuh-modules-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + ${'Invalid name parameter'} | ${'..%2fwazuh-modules-overview-general-1234.pdf'} | ${400} | ${'fails to match the required pattern'} + ${'Invalid name parameter'} | ${'custom..%2fwazuh-modules-overview-general-1234.pdf'} | ${400} | ${'fails to match the required pattern'} + `(`$titleTest -GET /reports/$name - $responseStatusCode + message: $responseBodyMessage`, async ({ name, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .get(`/reports/${name}`) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); +}); + +describe('[security] DELETE /reports/{name}', () => { + it.each` + titleTest | name | responseStatusCode | responseBodyMessage + ${'Route not found'} | ${'../wazuh-modules-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + ${'Invalid name parameter'} | ${'..%2fwazuh-modules-overview-general-1234.pdf'} | ${400} | ${'fails to match the required pattern'} + ${'Invalid name parameter'} | ${'custom..%2fwazuh-modules-overview-general-1234.pdf'} | ${400} | ${'fails to match the required pattern'} + `(`$titleTest - DELETE /reports/$name - $responseStatusCode + message: $responseBodyMessage`, async ({ name, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .delete(`/reports/${name}`) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); +}); + +describe('[security] POST /reports', () => { + it.each` + titleTest | tab | isAgents | responseStatusCode | responseBodyMessage + ${'Module - Invalid tab parameter'} | ${'../general'} | ${false} | ${400} | ${'fails because ["tab" must be one of'} + ${'Module - Invalid isAgents parameter'} | ${'general'} | ${'../001'} | ${400} | ${'fails to match the required pattern'} + `(`$titleTest - POST /reports - $responseStatusCode + message: $responseBodyMessage + tab: $tab + isAgents: $isAgents`, async ({ tab, isAgents, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports`) + .send({ + 'array': [], + 'filters': [], + 'time': { + 'from': '', + 'to': '' + }, + 'searchBar': '', + 'tables': [], + 'tab': tab, + 'section': 'overview', + 'isAgents': isAgents, + 'browserTimezone': '' + }) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); + + it.each` + titleTest | groupID | responseStatusCode | responseBodyMessage + ${'Group - Invalid groupID parameter'} | ${'../general'} | ${400} | ${'fails because ["groupID" with value'} + ${'Group - Invalid groupID parameter'} | ${'..%2fgeneral'} | ${400} | ${'fails because ["groupID" with value'} + `(`$titleTest - POST /reports - $responseStatusCode + message: $responseBodyMessage + groupID: $groupID`, async ({ groupID, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports`) + .send({ + 'array': [], + 'time': '', + 'searchBar': '', + 'tables': [], + 'tab': 'groupConfig', + 'browserTimezone': '', + 'components': { + '0': true, + '1': true + }, + 'groupID': groupID + }) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); + + it.each` + titleTest | agentID | responseStatusCode | responseBodyMessage + ${'Agent configuration - Invalid agentID parameter'} | ${'../001'} | ${400} | ${'fails because ["agentID" with value'} + ${'Agent configuration - Invalid agentID parameter'} | ${'..%2f001'} | ${400} | ${'fails because ["agentID" with value'} + `(`$titleTest - POST /reports - $responseStatusCode + message: $responseBodyMessage + agentID: $agentID`, async ({ agentID, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports`) + .send({ + 'array': [], + 'time': '', + 'searchBar': '', + 'tables': [], + 'tab': 'agentConfig', + 'browserTimezone': 'Europe/Madrid', + 'components': { + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '6': true, + '7': true, + '8': true, + '9': true, + '10': true, + '12': true, + '13': true + }, + 'agentID': agentID + }) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); + + it.each` + titleTest | isAgents | responseStatusCode | responseBodyMessage + ${'Agent inventory - Invalid isAgents parameter'} | ${'../001'} | ${400} | ${'"isAgents" with value "../001" fails to match the required pattern: /^\\d{3,}$/]'} + ${'Agent inventory - Invalid isAgents parameter'} | ${'..%2f001'} | ${400} | ${'"isAgents" with value "..%2f001" fails to match the required pattern: /^\\d{3,}$/]'} + `(`$titleTest - POST /reports - $responseStatusCode + message: $responseBodyMessage + isAgents: $isAgents`, async ({ isAgents, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports`) + .send({ + 'array': [], + 'filters': [], + 'time': {}, + 'searchBar': false, + 'tables': [], + 'tab': 'syscollector', + 'section': 'agents', + 'isAgents': isAgents, + 'browserTimezone': '' + }) + .expect(responseStatusCode); + if(responseBodyMessage && response.body?.message){ + expect(response.body.message).toMatch(responseBodyMessage) + }; + }); + +}); + diff --git a/server/routes/wazuh-reporting.js b/server/routes/wazuh-reporting.js index 73fdd1c8bc..0ef6ace7f2 100644 --- a/server/routes/wazuh-reporting.js +++ b/server/routes/wazuh-reporting.js @@ -10,16 +10,56 @@ * Find more information about this on the LICENSE file. */ import { WazuhReportingCtrl } from '../controllers'; +import Joi from 'joi'; export function WazuhReportingRoutes(server) { const ctrl = new WazuhReportingCtrl(server); + const reportFilenameValidation = Joi.string().regex(/^[\w\-\.]+\.pdf$/).required(); + // Builds a PDF report from multiple PNG images server.route({ method: 'POST', path: '/reports', handler(req, res) { return ctrl.report(req, res); + }, + options: { + validate: { + payload: Joi.object({ + agentID: Joi.string().min(3).regex(/^\d{3,}$/), + array: Joi.array().items(Joi.any()).required(), + browserTimezone: Joi.string().required(), + components: Joi.object().pattern(/^\d+$/, Joi.boolean()), + filters: Joi.any(), + // groupID validation by regex: + // The group name must match the regex: /^[a-zA-Z0-9_\-.%]+$/ + // If we try to create a group whose name includes the % character, the Wazuh API has an error. + // So, we remove the % character to validate the group for generating group configurtion reports + // https://github.com/wazuh/wazuh-api/blob/v3.13.4/controllers/agents.js#L802 + // https://github.com/wazuh/wazuh-api/blob/master/helpers/filters.js#L25-L44 + // https://github.com/wazuh/wazuh-api/blob/v3.13.4/helpers/input_validation.js#L26-L28 + groupID: Joi.string().regex(/^[a-zA-Z0-9_\-.]+$/), + isAgents: Joi.alternatives( + Joi.boolean(), + Joi.string().min(3).regex(/^\d{3,}$/) + ), + searchBar: Joi.alternatives( + Joi.boolean(), + Joi.string().allow('') + ), + section: Joi.any().when('tab', { is: Joi.string().valid('overview','agents'), then: Joi.required() }), + tab: Joi.string().valid('general','fim','aws','pm','audit','sca','ciscat','vuls','mitre','virustotal','docker','osquery','oscap','pci','hipaa','nist','gdpr','tsc', 'agentConfig', 'groupConfig', 'syscollector'), + tables: Joi.any(), + time: Joi.alternatives( + Joi.string().valid(''), + Joi.object({ + from: Joi.string().required(), + to: Joi.string().required() + }) + ) + }) + } } }); @@ -29,6 +69,13 @@ export function WazuhReportingRoutes(server) { path: '/reports/{name}', handler(req, res) { return ctrl.getReportByName(req, res); + }, + options: { + validate: { + params: Joi.object({ + name: reportFilenameValidation + }) + } } }); @@ -38,6 +85,13 @@ export function WazuhReportingRoutes(server) { path: '/reports/{name}', handler(req, res) { return ctrl.deleteReportByName(req, res); + }, + options: { + validate: { + params: Joi.object({ + name: reportFilenameValidation + }) + } } }); diff --git a/test/jest/jest.config.js b/test/jest/jest.config.js new file mode 100644 index 0000000000..bb5e60fcc3 --- /dev/null +++ b/test/jest/jest.config.js @@ -0,0 +1,26 @@ +import path from 'path'; + +const kbnDir = path.resolve(__dirname, '../../../../'); + +export default { + rootDir: path.resolve(__dirname, '../..'), + roots: [ + '/public', + '/server' + ], + modulePathIgnorePatterns: [ + '__fixtures__/', + 'target/', + ], + testMatch: [ + '**/*.test.{js,ts,tsx}', + ], + transform: { + '^.+\\.js$': `${kbnDir}/src/dev/jest/babel_transform.js`, + '^.+\\.tsx?$': `${kbnDir}/src/dev/jest/babel_transform.js`, + '^.+\\.html?$': `${kbnDir}/src/dev/jest/babel_transform.js`, + }, + transformIgnorePatterns: [ + '[/\\\\]node_modules[/\\\\].+\\.js$', + ], +}; \ No newline at end of file diff --git a/test/jest/jest.js b/test/jest/jest.js new file mode 100644 index 0000000000..524e321f75 --- /dev/null +++ b/test/jest/jest.js @@ -0,0 +1,7 @@ +const path = require('path'); +process.argv.push('--config', path.resolve(__dirname, './jest.config.js')); + +require('../../../../src/setup_node_env'); +const jest = require('../../../../node_modules/jest'); + +jest.run(process.argv.slice(2)); \ No newline at end of file