Skip to content

Commit 37e1972

Browse files
committed
Update parser-sdk & nmap parser to run using ESM
Signed-off-by: Jannik Hollenbach <jannik.hollenbach@owasp.org>
1 parent c703b57 commit 37e1972

8 files changed

Lines changed: 717 additions & 1663 deletions

File tree

parser-sdk/nodejs/package-lock.json

Lines changed: 547 additions & 1508 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

parser-sdk/nodejs/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
{
22
"name": "@securecodebox/parser-sdk-nodejs",
33
"version": "1.0.0",
4+
"type": "module",
45
"description": "Handles external communication required for all secureCodeBox parsers",
56
"main": "parser-wrapper.js",
67
"keywords": [],
78
"author": "iteratec GmbH",
89
"license": "Apache-2.0",
910
"dependencies": {
10-
"@kubernetes/client-node": "^0.22.3",
11+
"@kubernetes/client-node": "^1.3.0",
1112
"ajv": "^8.17.1",
1213
"ajv-draft-04": "^1.0.0",
1314
"ajv-formats": "^3.0.1",
1415
"axios": "^1.7.9",
1516
"jsonpointer": "^5.0.1",
1617
"ws": "^8.13.0"
1718
}
18-
}
19+
}

parser-sdk/nodejs/parser-utils.js

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,31 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
const { readFile } = require("node:fs/promises");
6-
const { randomUUID } = require("node:crypto");
7-
const Ajv = require("ajv-draft-04");
8-
const addFormats = require("ajv-formats");
9-
const jsonpointer = require("jsonpointer");
5+
import { readFile } from "node:fs/promises";
6+
import { randomUUID } from "node:crypto";
7+
8+
import addFormats from "ajv-formats";
9+
import { get } from "jsonpointer";
10+
import Ajv from "ajv-draft-04";
1011

1112
const ajv = new Ajv();
1213
addFormats(ajv);
1314

14-
function addIdsAndDates(findings) {
15-
return findings.map((finding) => {
16-
return {
17-
...finding,
18-
id: randomUUID(),
19-
parsed_at: new Date().toISOString(),
20-
};
21-
});
15+
export async function validate(findings) {
16+
const jsonSchemaString = await readFile(
17+
import.meta.dirname + "/findings-schema.json",
18+
"utf8",
19+
);
20+
const jsonSchema = JSON.parse(jsonSchemaString);
21+
const validator = ajv.compile(jsonSchema);
22+
const valid = validator(findings);
23+
if (!valid) {
24+
const errorMessage = generateErrorMessage(validator.errors, findings);
25+
throw new Error(errorMessage);
26+
}
2227
}
2328

24-
function addScanMetadata(findings, scan) {
29+
export function addScanMetadata(findings, scan) {
2530
const scanMetadata = {
2631
created_at: scan.metadata.creationTimestamp,
2732
name: scan.metadata.name,
@@ -35,21 +40,18 @@ function addScanMetadata(findings, scan) {
3540
}));
3641
}
3742

38-
async function validateAgainstJsonSchema(findings) {
39-
const jsonSchemaString = await readFile(
40-
__dirname + "/findings-schema.json",
41-
"utf8"
42-
);
43-
const jsonSchema = JSON.parse(jsonSchemaString);
44-
const validator = ajv.compile(jsonSchema);
45-
const valid = validator(findings);
46-
if (!valid) {
47-
const errorMessage = generateErrorMessage(validator.errors, findings);
48-
throw new Error(errorMessage);
49-
}
43+
export function addIdsAndDates(findings) {
44+
return findings.map((finding) => {
45+
return {
46+
...finding,
47+
id: randomUUID(),
48+
parsed_at: new Date().toISOString(),
49+
};
50+
});
5051
}
5152

52-
async function addSampleIdsAndDatesAndValidate(findings) {
53+
// used for tests to validate if the parser sets all required fields correctly. Adds sample IDs and Dates to the findings which would normally be set by the parser-sdk.
54+
export async function validateParser(findings) {
5355
const sampleScan = {
5456
metadata: {
5557
creationTimestamp: new Date().toISOString(),
@@ -59,23 +61,21 @@ async function addSampleIdsAndDatesAndValidate(findings) {
5961
spec: {
6062
scanType: "sample-scan-type",
6163
},
62-
}
64+
};
6365
// add sample IDs and Dates only if the findings Array is not empty
64-
const extendedData = addScanMetadata(addIdsAndDates(findings),sampleScan);
65-
return validateAgainstJsonSchema(extendedData);
66+
const extendedData = addScanMetadata(addIdsAndDates(findings), sampleScan);
67+
return validate(extendedData);
6668
}
6769

6870
function generateErrorMessage(errors, findings) {
69-
errors = errors.map((error) => {
70-
return {
71-
...error,
72-
invalidValue: jsonpointer.get(findings, error.instancePath),
73-
};
74-
});
75-
return JSON.stringify(errors, null, 2);
71+
return JSON.stringify(
72+
errors.map((error) => {
73+
return {
74+
...error,
75+
invalidValue: get(findings, error.instancePath),
76+
};
77+
}),
78+
null,
79+
2,
80+
);
7681
}
77-
78-
module.exports.addIdsAndDates = addIdsAndDates;
79-
module.exports.addScanMetadata = addScanMetadata;
80-
module.exports.validate = validateAgainstJsonSchema;
81-
module.exports.validateParser = addSampleIdsAndDatesAndValidate;

parser-sdk/nodejs/parser-wrapper.js

Lines changed: 57 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,34 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
const axios = require("axios");
6-
const { parse } = require("./parser/parser");
7-
const { validate, addIdsAndDates, addScanMetadata } = require("./parser-utils");
8-
const k8s = require("@kubernetes/client-node");
9-
10-
const kc = new k8s.KubeConfig();
5+
import axios from "axios";
6+
import {
7+
KubeConfig,
8+
CustomObjectsApi,
9+
setHeaderOptions,
10+
PatchStrategy,
11+
} from "@kubernetes/client-node";
12+
13+
import { parse } from "./parser/parser.js";
14+
import { validate, addIdsAndDates, addScanMetadata } from "./parser-utils.js";
15+
16+
const kc = new KubeConfig();
1117
kc.loadFromCluster();
12-
const k8sApi = kc.makeApiClient(k8s.CustomObjectsApi);
18+
const k8sApi = kc.makeApiClient(CustomObjectsApi);
19+
1320
const scanName = process.env["SCAN_NAME"];
1421
const namespace = process.env["NAMESPACE"];
1522

1623
function severityCount(findings, severity) {
1724
return findings.filter(
1825
({ severity: findingSeverity }) =>
19-
findingSeverity.toUpperCase() === severity
26+
findingSeverity.toUpperCase() === severity,
2027
).length;
2128
}
2229

2330
async function uploadResultToFileStorageService(
2431
resultUploadUrl,
25-
findingsWithIdsAndDates
32+
findingsWithIdsAndDates,
2633
) {
2734
return axios
2835
.put(resultUploadUrl, findingsWithIdsAndDates, {
@@ -34,12 +41,12 @@ async function uploadResultToFileStorageService(
3441
// The request was made and the server responded with a status code
3542
// that falls out of the range of 2xx
3643
console.error(
37-
`Finding Upload Failed with Response Code: ${error.response.status}`
44+
`Finding Upload Failed with Response Code: ${error.response.status}`,
3845
);
3946
console.error(`Error Response Body: ${error.response.data}`);
4047
} else if (error.request) {
4148
console.error(
42-
"No response received from FileStorage when uploading finding"
49+
"No response received from FileStorage when uploading finding",
4350
);
4451
console.error(error);
4552
} else {
@@ -62,29 +69,28 @@ async function updateScanStatus(findings) {
6269
}
6370

6471
await k8sApi.patchNamespacedCustomObjectStatus(
65-
"execution.securecodebox.io",
66-
"v1",
67-
namespace,
68-
"scans",
69-
scanName,
7072
{
71-
status: {
72-
findings: {
73-
count: findings.length,
74-
severities: {
75-
informational: severityCount(findings, "INFORMATIONAL"),
76-
low: severityCount(findings, "LOW"),
77-
medium: severityCount(findings, "MEDIUM"),
78-
high: severityCount(findings, "HIGH"),
73+
group: "execution.securecodebox.io",
74+
version: "v1",
75+
namespace,
76+
plural: "scans",
77+
name: scanName,
78+
body: {
79+
status: {
80+
findings: {
81+
count: findings.length,
82+
severities: {
83+
informational: severityCount(findings, "INFORMATIONAL"),
84+
low: severityCount(findings, "LOW"),
85+
medium: severityCount(findings, "MEDIUM"),
86+
high: severityCount(findings, "HIGH"),
87+
},
88+
categories: Object.fromEntries(findingCategories.entries()),
7989
},
80-
categories: Object.fromEntries(findingCategories.entries()),
8190
},
8291
},
8392
},
84-
undefined,
85-
undefined,
86-
undefined,
87-
{ headers: { "content-type": "application/merge-patch+json" } }
93+
setHeaderOptions("Content-Type", PatchStrategy.MergePatch),
8894
);
8995
console.log("Updated status successfully");
9096
} catch (err) {
@@ -96,32 +102,29 @@ async function updateScanStatus(findings) {
96102

97103
async function extractScan() {
98104
try {
99-
const { body } = await k8sApi.getNamespacedCustomObject(
100-
"execution.securecodebox.io",
101-
"v1",
105+
return await k8sApi.getNamespacedCustomObject({
106+
group: "execution.securecodebox.io",
107+
version: "v1",
108+
plural: "scans",
109+
name: scanName,
102110
namespace,
103-
"scans",
104-
scanName
105-
);
106-
return body;
111+
});
107112
} catch (err) {
108113
console.error("Failed to get Scan from the kubernetes api");
109114
console.error(err);
110115
process.exit(1);
111116
}
112-
113117
}
114118

115119
async function extractParseDefinition(scan) {
116120
try {
117-
const { body } = await k8sApi.getNamespacedCustomObject(
118-
"execution.securecodebox.io",
119-
"v1",
121+
return await k8sApi.getNamespacedCustomObject({
122+
group: "execution.securecodebox.io",
123+
version: "v1",
124+
plural: "parsedefinitions",
125+
name: scan.status.rawResultType,
120126
namespace,
121-
"parsedefinitions",
122-
scan.status.rawResultType
123-
);
124-
return body;
127+
});
125128
} catch (err) {
126129
console.error("Failed to get ParseDefinition from the kubernetes api");
127130
console.error(err);
@@ -138,8 +141,8 @@ async function main() {
138141

139142
console.log("Fetching result file");
140143
let response;
141-
if(parseDefinition.spec.contentType === "Binary"){
142-
response = await axios.get(resultFileUrl, {responseType: 'arraybuffer'});
144+
if (parseDefinition.spec.contentType === "Binary") {
145+
response = await axios.get(resultFileUrl, { responseType: "arraybuffer" });
143146
} else {
144147
response = await axios.get(resultFileUrl);
145148
}
@@ -162,11 +165,15 @@ async function main() {
162165
console.log("Adding scan metadata to the findings");
163166
const findingsWithMetadata = addScanMetadata(findingsWithIdsAndDates, scan);
164167

165-
const crash_on_failed_validation = process.env["CRASH_ON_FAILED_VALIDATION"] === "true"
166-
console.log("Validating Findings. Environment variable CRASH_ON_FAILED_VALIDATION is set to %s", crash_on_failed_validation);
168+
const crash_on_failed_validation =
169+
process.env["CRASH_ON_FAILED_VALIDATION"] === "true";
170+
console.log(
171+
"Validating Findings. Environment variable CRASH_ON_FAILED_VALIDATION is set to %s",
172+
crash_on_failed_validation,
173+
);
167174
try {
168175
await validate(findingsWithMetadata);
169-
console.log("The Findings were successfully validated")
176+
console.log("The Findings were successfully validated");
170177
} catch (error) {
171178
console.error("The Findings Validation failed with error(s):");
172179
console.error(error);
@@ -179,15 +186,9 @@ async function main() {
179186

180187
console.log(`Uploading results to the file storage service`);
181188

182-
await uploadResultToFileStorageService(
183-
resultUploadUrl,
184-
findingsWithMetadata
185-
);
189+
await uploadResultToFileStorageService(resultUploadUrl, findingsWithMetadata);
186190

187191
console.log(`Completed parser`);
188192
}
189193

190194
main();
191-
192-
module.exports.addIdsAndDates = addIdsAndDates;
193-
module.exports.addScanMetadata = addScanMetadata;

scanners/nmap/parser/package-lock.json

Lines changed: 16 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)