bf76bb7a1d
Co-authored-by: Harry Hogg <hhogg@spotify.com> Signed-off-by: Himanshu Mishra <himanshu@orkohunter.net>
149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
/*
|
|
* Copyright 2021 The Backstage Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
import { Octokit } from '@octokit/rest';
|
|
// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator
|
|
import synkJsonOutput from '../snyk.json';
|
|
|
|
// Pattern for a GitHub Issue title
|
|
// Snyk vulnerability [Vulnerability ID]
|
|
|
|
type Vulnerability = {
|
|
description: string;
|
|
snykId: string;
|
|
packages: Set<string>;
|
|
};
|
|
|
|
// Remember to fix me!
|
|
const GH_OWNER = 'orkohunter';
|
|
const GH_REPO = 'backstage';
|
|
|
|
const octokit = new Octokit({
|
|
auth: process.env.GITHUB_TOKEN,
|
|
});
|
|
|
|
const fetchSnykGithubIssueMap = async (): Promise<Record<string, number>> => {
|
|
const snykGithubIssueMap: Record<string, number> = {};
|
|
|
|
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
|
|
// TODO(Harry/Himanshu): Use a CLI flag for these values.
|
|
owner: GH_OWNER,
|
|
repo: GH_REPO,
|
|
per_page: 100,
|
|
labels: 'snyk-vulnerability',
|
|
});
|
|
|
|
for await (const { data: issues } of iterator) {
|
|
for (const issue of issues) {
|
|
// Gets the Vulnerability ID from square braces
|
|
const match = /\([([A-Z0-9-]+)\])/.exec(issue.title);
|
|
|
|
if (match && match[1]) {
|
|
snykGithubIssueMap[match[1]] = issue.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return snykGithubIssueMap;
|
|
};
|
|
|
|
const generateIssueBody = (vulnerability: Vulnerability) => {
|
|
let issueBody = '';
|
|
issueBody += '## Affecting Packages/Plugins\n';
|
|
vulnerability.packages.forEach(pkgName => {
|
|
issueBody += `* ${pkgName}\n`;
|
|
});
|
|
// TODO: Use displayTargetFile in snyk.json to create hyperlinks
|
|
issueBody += '\n';
|
|
issueBody += vulnerability.description;
|
|
return issueBody;
|
|
};
|
|
|
|
const createGithubIssue = (vulnerability: Vulnerability) => {
|
|
console.log(
|
|
`Create issue for vulnerability ${
|
|
vulnerability.snykId
|
|
} affecting packages ${Array.from(vulnerability.packages)}`,
|
|
);
|
|
octokit.issues.create({
|
|
owner: GH_OWNER,
|
|
repo: GH_REPO,
|
|
title: `Snyk vulnerability [${vulnerability.snykId}]`,
|
|
labels: ['snyk-vulnerability', 'help wanted'],
|
|
body: generateIssueBody(vulnerability),
|
|
});
|
|
};
|
|
|
|
const updateGithubIssue = (
|
|
githubIssueId: number,
|
|
vulnerability: Vulnerability,
|
|
) => {
|
|
console.log(
|
|
`Update issue ${githubIssueId} for vulnerability ${vulnerability.snykId}`,
|
|
);
|
|
// TODO(hhogg): Update github issue with the contents from a Snyk issue.
|
|
};
|
|
|
|
const closeGithubIssue = (githubIssueId: number) => {
|
|
console.log(`Delete issue ${githubIssueId}`);
|
|
// TODO(hhogg): Delete a github issue
|
|
};
|
|
|
|
async function main() {
|
|
const snykGithubIssueMap = await fetchSnykGithubIssueMap();
|
|
const vulnerabilityStore: Record<string, Vulnerability> = {};
|
|
|
|
// Group the Snyk vulnerabilities, and aggregate the affecting packages.
|
|
synkJsonOutput.forEach(({ projectName, vulnerabilities }) => {
|
|
vulnerabilities.forEach(
|
|
({ id, description }: { id: string; description: string }) => {
|
|
if (id !== undefined && description !== undefined) {
|
|
if (vulnerabilityStore[id]) {
|
|
vulnerabilityStore[id].packages.add(projectName);
|
|
} else {
|
|
vulnerabilityStore[id] = {
|
|
description,
|
|
snykId: id,
|
|
packages: new Set([projectName]),
|
|
};
|
|
}
|
|
}
|
|
},
|
|
);
|
|
});
|
|
|
|
// Loop over the grouped vulnerabilities and create/update accordingly
|
|
Object.entries(vulnerabilityStore).forEach(([id, vulnerability]) => {
|
|
if (snykGithubIssueMap[id]) {
|
|
updateGithubIssue(snykGithubIssueMap[id], vulnerability);
|
|
} else {
|
|
createGithubIssue(vulnerability);
|
|
}
|
|
});
|
|
|
|
// Loop over the Github issues and delete accordingly.
|
|
Object.entries(snykGithubIssueMap).forEach(([snykId, githubIssueId]) => {
|
|
if (!snykGithubIssueMap[snykId]) {
|
|
closeGithubIssue(githubIssueId);
|
|
}
|
|
});
|
|
}
|
|
|
|
main().catch(error => {
|
|
console.error(error.stack);
|
|
process.exit(1);
|
|
});
|