// SPDX-FileCopyrightText: the secureCodeBox authors // // SPDX-License-Identifier: Apache-2.0 import { KubeConfig, CustomObjectsApi, setHeaderOptions, PatchStrategy, } from "@kubernetes/client-node"; import { handle } from "./hook/hook.js"; const kc = new KubeConfig(); kc.loadFromCluster(); const k8sApi = kc.makeApiClient(CustomObjectsApi); const scanName = process.env["SCAN_NAME"]; const namespace = process.env["NAMESPACE"]; function downloadFile(url) { return fetch(url); } async function getRawResults() { const rawResultUrl = process.argv[2]; const response = await downloadFile(rawResultUrl); console.log(`Fetched raw result file contents from the file storage`); return await response.text(); } async function getFindings() { const findingsUrl = process.argv[3]; const response = await downloadFile(findingsUrl); const findings = await response.json(); console.log(`Fetched ${findings.length} findings from the file storage`); return findings; } async function uploadFile(url, fileContents) { try { const response = await fetch(url, { method: "PUT", headers: { "content-type": "" }, body: fileContents, }); if (!response.ok) { // The request was made and the server responded with a status code // that falls out of the range of 2xx const error = new Error(`HTTP error! status: ${response.status}`); error.response = response; throw error; } } catch (error) { if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error( `File Upload Failed with Response Code: ${error.response.status}`, ); const errorBody = await error.response.text(); console.error(`Error Response Body: ${errorBody}`); } else { // Something happened in setting up the request that triggered an Error console.error("Error uploading findings from hook", error.message); } process.exit(1); } } function updateRawResults(fileContents) { const rawResultUploadUrl = process.argv[4]; if (rawResultUploadUrl === undefined) { console.error( "Tried to upload RawResults but didn't find a valid URL to upload the findings to.", ); console.error("This probably means that this hook is a ReadOnly hook."); console.error( "If you want to change RawResults you'll need to use a ReadAndWrite Hook.", ); } return uploadFile(rawResultUploadUrl, fileContents); } function severityCount(findings, severity) { return findings.filter( ({ severity: findingSeverity }) => findingSeverity.toUpperCase() === severity, ).length; } async function updateFindings(findings) { const findingsUploadUrl = process.argv[5]; if (findingsUploadUrl === undefined) { console.error( "Tried to upload Findings but didn't find a valid URL to upload the findings to.", ); console.error("This probably means that this hook is a ReadOnly hook."); console.error( "If you want to change Findings you'll need to use a ReadAndWrite Hook.", ); } await uploadFile(findingsUploadUrl, JSON.stringify(findings)); // Update the scans findingStats (severities, categories, or the count) of the scan results const findingCategories = new Map(); for (const { category } of findings) { if (findingCategories.has(category)) { findingCategories.set(category, findingCategories.get(category) + 1); } else { findingCategories.set(category, 1); } } await k8sApi.patchNamespacedCustomObjectStatus( { group: "execution.securecodebox.io", version: "v1", namespace, plural: "scans", name: scanName, body: { status: { findings: { count: findings.length, severities: { informational: severityCount(findings, "INFORMATIONAL"), low: severityCount(findings, "LOW"), medium: severityCount(findings, "MEDIUM"), high: severityCount(findings, "HIGH"), }, categories: Object.fromEntries(findingCategories.entries()), }, }, }, }, setHeaderOptions("Content-Type", PatchStrategy.MergePatch), ); console.log("Updated status successfully"); } async function main() { console.log(`Starting hook for Scan "${scanName}"`); let scan; try { scan = await k8sApi.getNamespacedCustomObject({ group: "execution.securecodebox.io", version: "v1", namespace: namespace, plural: "scans", name: scanName, }); } catch (err) { console.error("Failed to get Scan from the kubernetes api"); console.error(err); process.exit(1); } try { await handle({ getRawResults, getFindings, updateRawResults, updateFindings, scan, }); } catch (error) { console.error("Error was thrown while running hooks handle function"); console.error(error); process.exit(1); } console.log(`Hook completed`); } main();