From 42281d49fb428bfdbb421596eb7dd61514bed607 Mon Sep 17 00:00:00 2001 From: conleth Date: Tue, 25 Nov 2025 10:31:12 -0800 Subject: [PATCH 1/4] persistence-elastic: handle es8 bulk response Signed-off-by: conleth --- hooks/persistence-elastic/hook/hook.js | 5 ++- hooks/persistence-elastic/hook/hook.test.js | 42 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/hooks/persistence-elastic/hook/hook.js b/hooks/persistence-elastic/hook/hook.js index e9ea8077e6..9079005a56 100644 --- a/hooks/persistence-elastic/hook/hook.js +++ b/hooks/persistence-elastic/hook/hook.js @@ -105,9 +105,10 @@ export async function handle({ }, ]); - const { body: bulkResponse } = await client.bulk({ refresh: true, body }); + const bulkResponseRaw = await client.bulk({ refresh: true, body }); + const bulkResponse = bulkResponseRaw?.body ?? bulkResponseRaw; - if (bulkResponse.errors) { + if (bulkResponse?.errors) { console.error("Bulk Request had errors:"); console.log(bulkResponse); } diff --git a/hooks/persistence-elastic/hook/hook.test.js b/hooks/persistence-elastic/hook/hook.test.js index 08b5e7b16e..125717f8c9 100644 --- a/hooks/persistence-elastic/hook/hook.test.js +++ b/hooks/persistence-elastic/hook/hook.test.js @@ -218,3 +218,45 @@ test("should append week format like yyyy/'W'W -> 2020/W46", async () => { index: `scb_2020/W46`, }); }); + +test("should handle elasticsearch v8 bulk response shape", async () => { + const findings = [ + { + id: "4560b3e6-1219-4f5f-9b44-6579f5a32407", + name: "Port 5601 is open", + category: "Open Port", + }, + ]; + + const v8BulkResponse = { errors: true, items: [] }; + + const v8Client = { + indices: { + create: jest.fn(), + }, + index: jest.fn(), + bulk: jest.fn(() => v8BulkResponse), + }; + + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {}); + + try { + await handle({ + getFindings: async () => findings, + scan, + now: testDate, + tenant: "default", + appendNamespace: true, + client: v8Client, + }); + expect(v8Client.bulk).toHaveBeenCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith("Bulk Request had errors:"); + expect(consoleLogSpy).toHaveBeenCalledWith(v8BulkResponse); + } finally { + consoleErrorSpy.mockRestore(); + consoleLogSpy.mockRestore(); + } +}); From 6594e768cc3e4e3b35f1ce3d7ee7b1a41bf1ec56 Mon Sep 17 00:00:00 2001 From: conleth Date: Tue, 25 Nov 2025 10:42:09 -0800 Subject: [PATCH 2/4] Adding my details Signed-off-by: conleth --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ad4696f13d..5fcf8d316a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -61,3 +61,4 @@ Committing with `git commit -s` will add the sign-off at the end of the commit m - Kai Schäfer - Joel Saß - Patrick Weiss +- Conleth Kennnedy From 7618b71e119277548f15bb406ac37027c4bed6a1 Mon Sep 17 00:00:00 2001 From: conleth Date: Tue, 25 Nov 2025 10:48:33 -0800 Subject: [PATCH 3/4] Extra n in my name Signed-off-by: conleth --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5fcf8d316a..d6071c23be 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -61,4 +61,4 @@ Committing with `git commit -s` will add the sign-off at the end of the commit m - Kai Schäfer - Joel Saß - Patrick Weiss -- Conleth Kennnedy +- Conleth Kennedy From 94b6871fdd3d85179a17c1633efc7b2d8385ae66 Mon Sep 17 00:00:00 2001 From: conleth Date: Tue, 9 Dec 2025 09:51:12 -0800 Subject: [PATCH 4/4] Refactor elastic hook tests to reduce duplication Signed-off-by: conleth --- hooks/persistence-elastic/hook/hook.test.js | 137 ++++++-------------- 1 file changed, 40 insertions(+), 97 deletions(-) diff --git a/hooks/persistence-elastic/hook/hook.test.js b/hooks/persistence-elastic/hook/hook.test.js index 125717f8c9..84c9184f0c 100644 --- a/hooks/persistence-elastic/hook/hook.test.js +++ b/hooks/persistence-elastic/hook/hook.test.js @@ -5,6 +5,7 @@ import { handle } from "./hook"; let elasticClient; +const buildGetFindings = (findings) => async () => findings; beforeEach(() => { elasticClient = { @@ -32,10 +33,36 @@ const scan = { const testDate = new Date("2020-11-11"); +const scanDocumentBody = { + "@timestamp": testDate, + id: scan.metadata.uid, + labels: scan.metadata.labels, + name: scan.metadata.name, + parameters: scan.spec.parameters, + scan_type: scan.spec.scanType, + type: "scan", +}; + +const expectScanIndexCalledWith = (index, client = elasticClient) => { + expect(client.index).toHaveBeenCalledTimes(1); + expect(client.index).toHaveBeenCalledWith({ + body: scanDocumentBody, + index, + }); +}; + +const findingsWithOpenPort = [ + { + id: "4560b3e6-1219-4f5f-9b44-6579f5a32407", + name: "Port 5601 is open", + category: "Open Port", + }, +]; + test("should only send scan summary document if no findings are passing in", async () => { const findings = []; - const getFindings = async () => findings; + const getFindings = buildGetFindings(findings); await handle({ getFindings, @@ -46,34 +73,12 @@ test("should only send scan summary document if no findings are passing in", asy client: elasticClient, }); - expect(elasticClient.index).toHaveBeenCalledTimes(1); - expect(elasticClient.index).toHaveBeenCalledWith({ - body: { - "@timestamp": testDate, - id: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", - labels: { - company: "iteratec", - }, - name: "demo-scan", - parameters: ["-Pn", "localhost"], - scan_type: "Nmap", - type: "scan", - }, - index: `scb_default_2020-11-11`, - }); + expectScanIndexCalledWith(`scb_default_2020-11-11`); expect(elasticClient.bulk).not.toHaveBeenCalled(); }); test("should send findings to elasticsearch with given prefix", async () => { - const findings = [ - { - id: "4560b3e6-1219-4f5f-9b44-6579f5a32407", - name: "Port 5601 is open", - category: "Open Port", - }, - ]; - - const getFindings = async () => findings; + const getFindings = buildGetFindings(findingsWithOpenPort); await handle({ getFindings, @@ -85,21 +90,7 @@ test("should send findings to elasticsearch with given prefix", async () => { client: elasticClient, }); - expect(elasticClient.index).toHaveBeenCalledTimes(1); - expect(elasticClient.index).toHaveBeenCalledWith({ - body: { - "@timestamp": testDate, - id: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", - labels: { - company: "iteratec", - }, - name: "demo-scan", - parameters: ["-Pn", "localhost"], - scan_type: "Nmap", - type: "scan", - }, - index: `myPrefix_default_2020-11-11`, - }); + expectScanIndexCalledWith(`myPrefix_default_2020-11-11`); expect(elasticClient.bulk).toHaveBeenCalledTimes(1); expect(elasticClient.bulk).toHaveBeenCalledWith({ @@ -130,7 +121,7 @@ test("should send findings to elasticsearch with given prefix", async () => { test("should not append namespace if 'appendNamespace' is null", async () => { const findings = []; - const getFindings = async () => findings; + const getFindings = buildGetFindings(findings); await handle({ getFindings, @@ -140,27 +131,13 @@ test("should not append namespace if 'appendNamespace' is null", async () => { client: elasticClient, }); - expect(elasticClient.index).toBeCalledTimes(1); - expect(elasticClient.index).toBeCalledWith({ - body: { - "@timestamp": testDate, - id: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", - labels: { - company: "iteratec", - }, - name: "demo-scan", - parameters: ["-Pn", "localhost"], - scan_type: "Nmap", - type: "scan", - }, - index: `scb_2020-11-11`, - }); + expectScanIndexCalledWith(`scb_2020-11-11`); }); test("should append date format yyyy", async () => { const findings = []; - const getFindings = async () => findings; + const getFindings = buildGetFindings(findings); await handle({ getFindings, @@ -171,27 +148,13 @@ test("should append date format yyyy", async () => { client: elasticClient, }); - expect(elasticClient.index).toBeCalledTimes(1); - expect(elasticClient.index).toBeCalledWith({ - body: { - "@timestamp": testDate, - id: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", - labels: { - company: "iteratec", - }, - name: "demo-scan", - parameters: ["-Pn", "localhost"], - scan_type: "Nmap", - type: "scan", - }, - index: `scb_2020`, - }); + expectScanIndexCalledWith(`scb_2020`); }); test("should append week format like yyyy/'W'W -> 2020/W46", async () => { const findings = []; - const getFindings = async () => findings; + const getFindings = buildGetFindings(findings); await handle({ getFindings, @@ -202,32 +165,12 @@ test("should append week format like yyyy/'W'W -> 2020/W46", async () => { client: elasticClient, }); - expect(elasticClient.index).toBeCalledTimes(1); - expect(elasticClient.index).toBeCalledWith({ - body: { - "@timestamp": testDate, - id: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", - labels: { - company: "iteratec", - }, - name: "demo-scan", - parameters: ["-Pn", "localhost"], - scan_type: "Nmap", - type: "scan", - }, - index: `scb_2020/W46`, - }); + expectScanIndexCalledWith(`scb_2020/W46`); }); test("should handle elasticsearch v8 bulk response shape", async () => { - const findings = [ - { - id: "4560b3e6-1219-4f5f-9b44-6579f5a32407", - name: "Port 5601 is open", - category: "Open Port", - }, - ]; - + const findings = findingsWithOpenPort; + const getFindings = buildGetFindings(findings); const v8BulkResponse = { errors: true, items: [] }; const v8Client = { @@ -245,7 +188,7 @@ test("should handle elasticsearch v8 bulk response shape", async () => { try { await handle({ - getFindings: async () => findings, + getFindings, scan, now: testDate, tenant: "default",