diff --git a/lib/instrumentation-security/core/event-constants.js b/lib/instrumentation-security/core/event-constants.js index 41c986b4..7c2a1b2c 100644 --- a/lib/instrumentation-security/core/event-constants.js +++ b/lib/instrumentation-security/core/event-constants.js @@ -40,7 +40,8 @@ const EVENT_CATEGORY = { UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT', REFLECTED_XSS: 'REFLECTED_XSS', XPATH: 'XPATH', - LDAP: 'LDAP' + LDAP: 'LDAP', + ELASTIC_SEARCH: 'ELASTIC_SEARCH', } module.exports = { diff --git a/lib/instrumentation-security/hooks/@elastic/nr-@elastic.js b/lib/instrumentation-security/hooks/@elastic/nr-@elastic.js new file mode 100644 index 00000000..2708e5e9 --- /dev/null +++ b/lib/instrumentation-security/hooks/@elastic/nr-@elastic.js @@ -0,0 +1,177 @@ +/* + * Copyright 2023 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: New Relic Pre-Release + */ + +const requestManager = require("../../core/request-manager"); + +const secUtils = require('../../core/sec-utils'); +const API = require("../../../nr-security-api"); +const securityMetaData = require('../../core/security-metadata'); +const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants'); +const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants'); +const logger = API.getLogger(); +const semver = require('semver'); + + +module.exports = initialize; + +/** + * Entry point of mysql and msyql2 module hooks + * @param {*} shim + * @param {*} mysql + * @param {*} moduleName + */ +function initialize(shim, elastic, moduleName) { + logger.info("Instrumenting", moduleName); + + const pkgVersion = shim.require('./package.json').version + if (semver.lt(pkgVersion, '7.13.0')) { + logger.debug(`ElasticSearch support is for versions 7.13.0 and above. Not instrumenting ${pkgVersion}.`) + return; + } + requestHook(shim, elastic.Transport.prototype, 'request'); + +} + +function requestHook(shim, mod, methodName) { + shim.wrap(mod, methodName, function makeWrapper(shim, fn) { + return function wrapper() { + try { + let args = arguments[0]; + let extractedReq = queryParser(args); + let payloadData = { + payload: extractedReq.query, + payloadType: extractedReq.operation, + collection: extractedReq.collection + } + + shim.interceptedArgs = payloadData; + const request = requestManager.getRequest(shim); + if (request) { + const traceObject = secUtils.getTraceObject(shim); + const secMetadata = securityMetaData.getSecurityMetaData(request, payloadData, traceObject, secUtils.getExecutionId(), EVENT_TYPE.NOSQL_DB_COMMAND, EVENT_CATEGORY.ELASTIC_SEARCH) + const secEvent = API.generateSecEvent(secMetadata); + API.sendEvent(secEvent); + } + } catch (error) { + logger.debug("Error in request hook of elastic serach:",error); + } + + return fn.apply(this, arguments); + }; + }); +} + +/** + * Convenience function to test if a value is a non-null object + * + * @param {object} thing Value to be tested + * @returns {boolean} whether or not the value is an object and not null + */ +function isSimpleObject(thing) { + return Object.prototype.toString.call(thing) === '[object Object]' && thing !== null +} + +/** + * Convenience function to test if an object is not empty + * + * @param {object} thing Value to be tested + * @returns {boolean} true if the value is an object, not null, and has keys + */ +function isNotEmpty(thing) { + return isSimpleObject(thing) && Object.keys(thing).length > 0 +} + +/** + * Parses the parameters sent to elasticsearch for collection, + * method, and query + * + * @param {object} params Query object received by the datashim. + * Required properties: path {string}, method {string}. + * Optional properties: querystring {string}, body {object}, and + * bulkBody {object} + * @returns {object} consisting of collection {string}, operation {string}, + * and query {string} + */ +function queryParser(params) { + const { collection, operation } = parsePath(params.path, params.method) + + // the substance of the query may be in querystring or in body. + let queryParam = {} + if (isNotEmpty(params.querystring)) { + queryParam = params.querystring + } + // let body or bulkBody override querystring, as some requests have both + if (isNotEmpty(params.body)) { + queryParam = params.body + } else if (Array.isArray(params.bulkBody) && params.bulkBody.length) { + queryParam = params.bulkBody + } + const clonedParams = Object.assign({}, queryParam); + let query = clonedParams; + query.path = params.path; + + return { + collection, + operation, + query, + } +} + + +/** + * Convenience function for parsing the params.path sent to the queryParser + * for normalized collection and operation + * + * @param {string} pathString params.path supplied to the query parser + * @param {string} method http method called by @elastic/elasticsearch + * @returns {object} consisting of collection {string} and operation {string} + */ +function parsePath(pathString, method) { + let collection + let operation + const defaultCollection = 'any' + const actions = { + GET: 'get', + PUT: 'create', + POST: 'create', + DELETE: 'delete', + HEAD: 'exists' + } + const suffix = actions[method] + + try { + const path = pathString.split('/') + if (method === 'PUT' && path.length === 2) { + collection = path?.[1] || defaultCollection + operation = `index.create` + return { collection, operation } + } + path.forEach((segment, idx) => { + const prev = idx - 1 + let opname + if (segment === '_search') { + collection = path?.[prev] || defaultCollection + operation = `search` + } else if (segment[0] === '_') { + opname = segment.substring(1) + collection = path?.[prev] || defaultCollection + operation = `${opname}.${suffix}` + } + }) + if (!operation && !collection) { + // likely creating an index--no underscore segments + collection = path?.[1] || defaultCollection + operation = `index.${suffix}` + } + } catch (e) { + logger.warn('Failed to parse path for operation and collection. Using defaults') + logger.warn(e) + collection = defaultCollection + operation = 'unknown' + } + + return { collection, operation } +} + diff --git a/lib/instrumentation-security/index.js b/lib/instrumentation-security/index.js index 2e471af1..88d9741a 100644 --- a/lib/instrumentation-security/index.js +++ b/lib/instrumentation-security/index.js @@ -190,6 +190,14 @@ newrelic.instrumentWebframework({ } }) +newrelic.instrumentWebframework({ + moduleName: '@elastic/elasticsearch', + isEsm: true, + onRequire: require('./hooks/@elastic/nr-@elastic'), + onError: function intrumentErrorHandler(err) { + logger.error(err.message, err.stack) + } +}) diff --git a/package-lock.json b/package-lock.json index ee6b43d2..6601a250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,8 @@ "ws": "^8.14.2" }, "devDependencies": { + "@elastic/elasticsearch": "^8.10.0", + "@elastic/elasticsearch-mock": "^2.0.0", "@hapi/hapi": "^21.3.0", "@koa/router": "^12.0.0", "@newrelic/newrelic-oss-cli": "^0.1.2", @@ -2198,6 +2200,67 @@ "node": ">=12.13.0" } }, + "node_modules/@elastic/elasticsearch": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.10.0.tgz", + "integrity": "sha512-RIEyqz0D18bz/dK+wJltaak+7wKaxDELxuiwOJhuMrvbrBsYDFnEoTdP/TZ0YszHBgnRPGqBDBgH/FHNgHObiQ==", + "dev": true, + "dependencies": { + "@elastic/transport": "^8.3.4", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@elastic/elasticsearch-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch-mock/-/elasticsearch-mock-2.0.0.tgz", + "integrity": "sha512-VACQF7GStt8DetY91aJhXCYog6zXM0Vyb62k592EEt3aB4plrOLot+JvlLMC4URjh2jt9qYfER9hn4AI+ULTSw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "find-my-way": "^5.2.0", + "into-stream": "^6.0.0" + } + }, + "node_modules/@elastic/elasticsearch-mock/node_modules/find-my-way": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-5.6.0.tgz", + "integrity": "sha512-pFTzbl2u+iSrvVOGtfKenvDmNIhNtEcwbzvRMfx3TGO69fbO5udgTKtAZAaUfIUrHQWLkkWvhNafNz179kaCIw==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@elastic/transport": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.3.4.tgz", + "integrity": "sha512-+0o8o74sbzu3BO7oOZiP9ycjzzdOt4QwmMEjFc1zfO7M0Fh7QX1xrpKqZbSd8vBwihXNlSq/EnMPfgD2uFEmFg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "hpagent": "^1.0.0", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0", + "tslib": "^2.4.0", + "undici": "^5.22.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@elastic/transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -2267,6 +2330,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.8.16", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.16.tgz", @@ -9240,6 +9312,16 @@ "node": ">= 0.6" } }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -9690,6 +9772,15 @@ "wbuf": "^1.1.0" } }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/html-entities": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.6.tgz", @@ -9964,6 +10055,22 @@ "node": ">= 0.4" } }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -12012,6 +12119,15 @@ "own-or": "^1.0.0" } }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -13145,6 +13261,12 @@ "node": ">=6" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -16361,6 +16483,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "5.28.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", + "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", @@ -18636,6 +18770,62 @@ "node-gyp-build": "^4.4.0" } }, + "@elastic/elasticsearch": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.10.0.tgz", + "integrity": "sha512-RIEyqz0D18bz/dK+wJltaak+7wKaxDELxuiwOJhuMrvbrBsYDFnEoTdP/TZ0YszHBgnRPGqBDBgH/FHNgHObiQ==", + "dev": true, + "requires": { + "@elastic/transport": "^8.3.4", + "tslib": "^2.4.0" + } + }, + "@elastic/elasticsearch-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch-mock/-/elasticsearch-mock-2.0.0.tgz", + "integrity": "sha512-VACQF7GStt8DetY91aJhXCYog6zXM0Vyb62k592EEt3aB4plrOLot+JvlLMC4URjh2jt9qYfER9hn4AI+ULTSw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "find-my-way": "^5.2.0", + "into-stream": "^6.0.0" + }, + "dependencies": { + "find-my-way": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-5.6.0.tgz", + "integrity": "sha512-pFTzbl2u+iSrvVOGtfKenvDmNIhNtEcwbzvRMfx3TGO69fbO5udgTKtAZAaUfIUrHQWLkkWvhNafNz179kaCIw==", + "dev": true, + "requires": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0" + } + } + } + }, + "@elastic/transport": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.3.4.tgz", + "integrity": "sha512-+0o8o74sbzu3BO7oOZiP9ycjzzdOt4QwmMEjFc1zfO7M0Fh7QX1xrpKqZbSd8vBwihXNlSq/EnMPfgD2uFEmFg==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "hpagent": "^1.0.0", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0", + "tslib": "^2.4.0", + "undici": "^5.22.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -18695,6 +18885,12 @@ } } }, + "@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "dev": true + }, "@grpc/grpc-js": { "version": "1.8.16", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.16.tgz", @@ -24118,6 +24314,16 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, "fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -24437,6 +24643,12 @@ "wbuf": "^1.1.0" } }, + "hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true + }, "html-entities": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.6.tgz", @@ -24641,6 +24853,16 @@ "side-channel": "^1.0.4" } }, + "into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, "ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -26287,6 +26509,12 @@ "own-or": "^1.0.0" } }, + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -27146,6 +27374,12 @@ "sparse-bitfield": "^3.0.3" } }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -29420,6 +29654,15 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici": { + "version": "5.28.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", + "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "dev": true, + "requires": { + "@fastify/busboy": "^2.0.0" + } + }, "unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", diff --git a/package.json b/package.json index e72d5577..ed3b1cba 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,8 @@ "ws": "^8.14.2" }, "devDependencies": { + "@elastic/elasticsearch": "^8.10.0", + "@elastic/elasticsearch-mock": "^2.0.0", "@hapi/hapi": "^21.3.0", "@koa/router": "^12.0.0", "@newrelic/newrelic-oss-cli": "^0.1.2", diff --git a/test/instrumentation-security/nr-@elastic.test.js b/test/instrumentation-security/nr-@elastic.test.js new file mode 100644 index 00000000..e35a3569 --- /dev/null +++ b/test/instrumentation-security/nr-@elastic.test.js @@ -0,0 +1,128 @@ +/* + * Copyright 2023 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: New Relic Pre-Release + */ + +'use strict' + +const test = require('tap').test; +const sinon = require('sinon'); +const utils = require('@newrelic/test-utilities') +var elastic = require('@elastic/elasticsearch'); +const Mock = require('@elastic/elasticsearch-mock') +const mock = new Mock(); + +const getElasticClient = () => { + var elasticClient = new elastic.Client({ + node: 'http://localhost:9200', + Connection: mock.getConnection() + }); + + mock.add({ + method: 'GET', + path: '/' + }, () => { + return { status: 'ok' } + }) + + mock.add({ + method: 'PUT', + path: '/:index' + }, () => { + return { status: 'index created' } + }) + + mock.add({ + method: 'POST', + path: '/:index/_search' + }, () => { + return { status: 'index found' } + }) + + mock.add({ + method: 'GET', + path: '/:index/_analyze' + }, () => { + return { status: 'ok' } + }) + + mock.add({ + method: 'POST', + path: '/_bulk' + }, () => { + return { status: 'ok' } + }) + + return elasticClient; +} + +test('elasticsearch', (t) => { + t.autoend(); + let helper = null; + let initialize = null; + let shim = null; + let client = null; + + t.beforeEach(() => { + helper = utils.TestAgent.makeInstrumented() + shim = helper.getShim(); + client = getElasticClient(); + initialize = require('../../lib/instrumentation-security/hooks/@elastic/nr-@elastic'); + sinon.stub(shim, 'getActiveSegment').returns({ transaction: { id: 1 } }); + sinon.stub(shim, 'require').returns({ version: '8.10.0' }); + initialize(shim, elastic, '@elastic/elasticsearch'); + }) + + t.afterEach(() => { + helper && helper.unload() + }) + + t.test('connection', async (t) => { + const res = await client.info() + t.equal(res.status, 'ok') + t.end(); + }) + + t.test('create index', async (t) => { + const res = await client.indices.create({ + index: "test" + }) + t.same(res.status, 'index created') + t.end(); + }) + + t.test('search', async (t) => { + const res = await client.search({ + index: "test", + type: "docType", + body: {explain: true}, + ignore_unavailable: true + }) + t.same(res.status, 'index found') + t.end(); + }) + + t.test('analyze', async (t) => { + const res = await client.indices.analyze({ index: "test" }) + t.same(res.status, 'ok') + t.end(); + }) + + t.test('bulk', async (t) => { + const res = await client.bulk({ + body: [ + { delete: { _index: 'test', _type: 'test', _id: 33 } }, + ] + } + ); + t.same(res.status, 'ok') + t.end(); + }) + + t.test('unsupported version', async (t) => { + sinon.restore() + sinon.stub(shim, 'require').returns({ version: '6.0.0' }); + initialize(shim, elastic, '@elastic/elasticsearch'); + t.end(); + }) +}) \ No newline at end of file