@zaproxy/actions-common-scans
Version:
201 lines (200 loc) • 9.76 kB
JavaScript
import fs from "fs";
import { Octokit as BaseOcto } from "@octokit/core";
import { throttling } from "@octokit/plugin-throttling";
import { retry } from "@octokit/plugin-retry";
import { requestLog } from "@octokit/plugin-request-log";
import actionHelper from "./action-helper.js";
const actionCommon = {
processReport: async (token, workSpace, plugins, currentRunnerID, issueTitle, repoName, allowIssueWriting = true, artifactName = "zap_scan", fetcher = fetch) => {
const jsonReportName = "report_json.json";
const mdReportName = "report_md.md";
const htmlReportName = "report_html.html";
if (!allowIssueWriting) {
await actionHelper.uploadArtifacts(workSpace, mdReportName, jsonReportName, htmlReportName, artifactName);
return;
}
let openIssue;
let currentReport;
let previousRunnerID;
let previousReport = {};
let create_new_issue = false;
const tmp = repoName.split("/");
const owner = tmp[0];
const repo = tmp[1];
const MyOctokit = BaseOcto.plugin(retry, throttling, requestLog);
if (fetcher !== fetch) {
MyOctokit.plugin(requestLog);
}
const octokit = new MyOctokit({
auth: token,
baseUrl: process.env.GITHUB_API_URL ?? "https://api.github.com",
throttle: {
onRateLimit: (retryAfter, options) => {
if (options?.request?.retryCount <= 2) {
console.log(`Hit rate limit. Retrying after ${retryAfter} seconds!`);
return true;
}
},
onSecondaryRateLimit: (retryAfter) => {
console.log(`Hit secondary rate limit. Retrying after ${retryAfter} seconds!`);
return true;
},
},
request: {
// Fetch is a type from @octokit/types, which is set to any..
// so we have to disable the eslint check for now.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
fetch: fetcher,
},
});
try {
const jReportFile = fs.readFileSync(`${workSpace}/${jsonReportName}`);
currentReport = JSON.parse(jReportFile.toString());
}
catch (e) {
console.log("Failed to locate the json report generated by ZAP Scan!");
return;
}
const issues = await octokit.request("GET /search/issues", {
q: encodeURI(`is:issue state:open repo:${owner}/${repo} ${issueTitle}`).replace(/%20/g, "+"),
sort: "updated",
});
// If there is no existing open issue then create a new issue
if (issues.data.items.length === 0) {
create_new_issue = true;
}
else {
let login = "github-actions[bot]";
try {
login = (await octokit.request("GET /user")).data.login;
}
catch (e) {
console.log(`Using ${login} to search for issues.`);
}
// Sometimes search API returns recently closed issue as an open issue
for (const issue of issues.data.items) {
if (issue.state === "open" && issue.user.login === login) {
openIssue = issue;
break;
}
}
if (openIssue === undefined) {
create_new_issue = true;
}
else {
console.log(`Ongoing open issue has been identified #${openIssue.number}`);
// If there is no comments then read the body
if (openIssue.comments === 0) {
previousRunnerID = actionHelper.getRunnerID(openIssue.body);
}
else {
const comments = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: owner,
repo: repo,
issue_number: openIssue.number,
});
let lastBotComment;
const lastCommentIndex = comments.data.length - 1;
for (let i = lastCommentIndex; i >= 0; i--) {
if (comments.data[i].user.login === login) {
lastBotComment = comments.data[i];
break;
}
}
if (lastBotComment === undefined) {
previousRunnerID = actionHelper.getRunnerID(openIssue.body);
}
else {
previousRunnerID = actionHelper.getRunnerID(lastBotComment.body);
}
}
if (previousRunnerID !== null) {
previousReport = await actionHelper.readPreviousReport(octokit, owner, repo, workSpace, previousRunnerID, artifactName);
if (previousReport === undefined) {
create_new_issue = true;
}
}
}
}
if (plugins.length !== 0) {
console.log(`${plugins.length} plugins will be ignored according to the rules configuration`);
currentReport = await actionHelper.filterReport(currentReport, plugins);
// Update the newly filtered report
fs.unlinkSync(`${workSpace}/${jsonReportName}`);
fs.writeFileSync(`${workSpace}/${jsonReportName}`, JSON.stringify(currentReport));
console.log("The current report is updated with the ignored alerts!");
}
const newAlertExits = actionHelper.checkIfAlertsExists(currentReport);
console.log(`Alerts present in the current report: ${newAlertExits.toString()}`);
if (!newAlertExits) {
// If no new alerts have been found close the issue
console.log("No new alerts have been identified by the ZAP Scan");
if (openIssue != null && openIssue.state === "open") {
// close the issue with a comment
console.log(`Starting to close the issue #${openIssue.number}`);
try {
await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: owner,
repo: repo,
issue_number: openIssue.number,
body: "All the alerts have been resolved during the last ZAP Scan!",
});
await octokit.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
owner: owner,
repo: repo,
issue_number: openIssue.number,
state: "closed",
});
console.log(`Successfully closed the issue #${openIssue.number}`);
}
catch (err) {
console.log(`Error occurred while closing the issue with a comment! err: ${err.toString()}`);
}
}
else if (openIssue != null && openIssue.state === "closed") {
console.log("No alerts found by ZAP Scan and no active issue is found in the repository, exiting the program!");
}
return;
}
const runnerInfo = `RunnerID:${currentRunnerID}`;
const runnerLink = `View the [following link](${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${currentRunnerID})` +
` to download the report.`;
if (create_new_issue) {
const msg = actionHelper.createMessage(currentReport.site, runnerInfo, runnerLink) +
"\n\n---\n[ZAP by Checkmarx](https://checkmarx.com/)";
const newIssue = await octokit.request("POST /repos/{owner}/{repo}/issues", {
owner: owner,
repo: repo,
title: issueTitle,
body: msg,
});
console.log(`Process completed successfully and a new issue #${newIssue.data.number} has been created for the ZAP Scan.`);
}
else {
const siteClone = actionHelper.generateDifference(currentReport, previousReport);
if (currentReport.updated) {
console.log("The current report has changes compared to the previous report");
try {
const msg = actionHelper.createMessage(siteClone, runnerInfo, runnerLink);
await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: owner,
repo: repo,
issue_number: openIssue.number,
body: msg,
});
console.log(`The issue #${openIssue.number} has been updated with the latest ZAP scan results!`);
console.log("ZAP Scan process completed successfully!");
}
catch (err) {
console.log(`Error occurred while updating the issue #${openIssue.number} with the latest ZAP scan: ${err.toString()}`);
}
}
else {
console.log("No changes have been observed from the previous scan and current scan!, exiting the program!");
}
}
await actionHelper.uploadArtifacts(workSpace, mdReportName, jsonReportName, htmlReportName, artifactName);
},
};
export const main = actionCommon;
export const helper = actionHelper;