Merge pull request #7746 from backstage/mob/snyk-issue-creation-workflow

Opening Github issues for Snyk vulnerabilities
This commit is contained in:
Patrik Oldsberg
2021-11-08 16:49:13 +01:00
committed by GitHub
4 changed files with 286 additions and 1 deletions
@@ -0,0 +1,23 @@
name: 'Snyk Github Issue Sync'
on:
schedule:
- cron: '0 */4 * * *'
jobs:
sync:
steps:
- uses: actions/checkout@v2
- name: Create Snyk report
uses: snyk/actions/node@master
with:
args: >
--yarn-workspaces
--org=backstage-dgh
--strict-out-of-sync=false
--json-file-output=snyk.json
json: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Update Github issues
run: yarn ts-node scripts/snyk-github-issue-sync.ts
+3 -1
View File
@@ -58,9 +58,10 @@
"@microsoft/tsdoc": "^0.13.2"
},
"devDependencies": {
"@types/webpack": "^5.28.0",
"@changesets/cli": "^2.14.0",
"@octokit/rest": "^18.12.0",
"@spotify/prettier-config": "^11.0.0",
"@types/webpack": "^5.28.0",
"command-exists": "^1.2.9",
"concurrently": "^6.0.0",
"eslint-plugin-notice": "^0.9.10",
@@ -68,6 +69,7 @@
"husky": "^6.0.0",
"lerna": "^4.0.0",
"lint-staged": "^11.1.2",
"minimist": "^1.2.5",
"prettier": "^2.2.1",
"shx": "^0.3.2",
"yarn-lock-check": "^1.0.5"
+200
View File
@@ -0,0 +1,200 @@
/*
* 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 import/no-extraneous-dependencies */
import { Octokit } from '@octokit/rest';
import minimist from 'minimist';
// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator
import synkJsonOutput from '../snyk.json';
type Vulnerability = {
description: string;
packages: {
name: string;
target: string;
}[];
snykId: string;
};
const argv = minimist(process.argv.slice(2));
const GH_OWNER = 'backstage';
const GH_REPO = 'backstage';
const SNYK_GH_LABEL = 'snyk-vulnerability';
const SNYK_ID_REGEX = /\[([A-Z0-9-:]+)]/i;
const isDryRun = 'dryrun' in argv;
if (!process.env.GITHUB_TOKEN) {
console.error('GITHUB_TOKEN is not set. Please provide a Github token');
process.exit(1);
}
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
if (isDryRun) {
console.log(
'⚠️ Running in dryrun mode, no issues will be updated on Github ⚠️',
);
}
const fetchSnykGithubIssueMap = async (): Promise<Record<string, number>> => {
const snykGithubIssueMap: Record<string, number> = {};
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
owner: GH_OWNER,
repo: GH_REPO,
per_page: 100,
state: 'open',
labels: SNYK_GH_LABEL,
});
for await (const { data: issues } of iterator) {
for (const issue of issues) {
// Gets the Vulnerability ID from square braces
const match = SNYK_ID_REGEX.exec(issue.title);
if (match && match[1]) {
snykGithubIssueMap[match[1]] = issue.number;
} else {
console.log(`Unmatched Snyk ID for ${issue.title}`);
}
}
}
return snykGithubIssueMap;
};
const generateIssueBody = (vulnerability: Vulnerability) => `
## Affecting Packages/Plugins
${Array.from(vulnerability.packages).map(
({ name, target }) => `* [${name}](${target})\n`,
)}
${vulnerability.description}
`;
const createGithubIssue = async (vulnerability: Vulnerability) => {
console.log(
`Create Github Issue for Snyk Vulnerability ${vulnerability.snykId}`,
);
vulnerability.packages.forEach(({ name, target }) => {
console.log(`- ${name} [${target}]`);
});
if (!isDryRun) {
await octokit.issues.create({
owner: GH_OWNER,
repo: GH_REPO,
title: `Snyk vulnerability [${vulnerability.snykId}]`,
labels: [SNYK_GH_LABEL, 'help wanted'],
body: generateIssueBody(vulnerability),
});
}
};
const updateGithubIssue = async (
githubIssueId: number,
vulnerability: Vulnerability,
) => {
console.log(
`Update Github Issue #${githubIssueId} for Snky Vulnerability ${vulnerability.snykId}`,
);
if (!isDryRun) {
await octokit.issues.update({
owner: GH_OWNER,
repo: GH_REPO,
issue_number: githubIssueId,
body: generateIssueBody(vulnerability),
});
}
};
const closeGithubIssue = async (githubIssueId: number) => {
console.log(`Closing Github Issue #${githubIssueId}`);
if (!isDryRun) {
await octokit.issues.update({
owner: GH_OWNER,
repo: GH_REPO,
issue_number: githubIssueId,
state: 'closed',
});
}
};
async function main() {
const snykGithubIssueMap = await fetchSnykGithubIssueMap();
const vulnerabilityStore: Record<string, Vulnerability> = {};
// Group the Snyk vulnerabilities, and link back to the affecting packages.
synkJsonOutput.forEach(
({ projectName, displayTargetFile, vulnerabilities }) => {
vulnerabilities.forEach(
({ id, description }: { id: string; description: string }) => {
if (id !== undefined && description !== undefined) {
if (vulnerabilityStore[id]) {
if (
!vulnerabilityStore[id].packages.some(
({ name }) => name === projectName,
)
) {
vulnerabilityStore[id].packages.push({
name: projectName,
target: displayTargetFile,
});
}
} else {
vulnerabilityStore[id] = {
description,
snykId: id,
packages: [
{
name: projectName,
target: displayTargetFile,
},
],
};
}
}
},
);
},
);
for (const [id, vulnerability] of Object.entries(vulnerabilityStore)) {
if (snykGithubIssueMap[id]) {
await updateGithubIssue(snykGithubIssueMap[id], vulnerability);
} else {
await createGithubIssue(vulnerability);
}
}
for (const [snykId, githubIssueId] of Object.entries(snykGithubIssueMap)) {
if (!vulnerabilityStore[snykId]) {
await closeGithubIssue(githubIssueId);
}
}
}
main().catch(error => {
console.error(error.stack);
process.exit(1);
});
+60
View File
@@ -4893,6 +4893,19 @@
before-after-hook "^2.1.0"
universal-user-agent "^6.0.0"
"@octokit/core@^3.5.1":
version "3.5.1"
resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b"
integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==
dependencies:
"@octokit/auth-token" "^2.4.4"
"@octokit/graphql" "^4.5.8"
"@octokit/request" "^5.6.0"
"@octokit/request-error" "^2.0.5"
"@octokit/types" "^6.0.3"
before-after-hook "^2.2.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^6.0.1":
version "6.0.3"
resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487"
@@ -4927,6 +4940,11 @@
"@octokit/types" "^6.12.2"
btoa-lite "^1.0.0"
"@octokit/openapi-types@^11.2.0":
version "11.2.0"
resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6"
integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==
"@octokit/openapi-types@^7.3.2":
version "7.3.2"
resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.3.2.tgz#065ce49b338043ec7f741316ce06afd4d459d944"
@@ -4937,6 +4955,13 @@
resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437"
integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==
"@octokit/plugin-paginate-rest@^2.16.8":
version "2.17.0"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7"
integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==
dependencies:
"@octokit/types" "^6.34.0"
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.7.0"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.0.tgz#6bb7b043c246e0654119a6ec4e72a172c9e2c7f3"
@@ -4949,6 +4974,11 @@
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44"
integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==
"@octokit/plugin-request-log@^1.0.4":
version "1.0.4"
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85"
integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==
"@octokit/plugin-rest-endpoint-methods@5.3.1":
version "5.3.1"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.3.1.tgz#deddce769b4ec3179170709ab42e4e9e6195aaa9"
@@ -4957,6 +4987,14 @@
"@octokit/types" "^6.16.2"
deprecation "^2.3.1"
"@octokit/plugin-rest-endpoint-methods@^5.12.0":
version "5.13.0"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba"
integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==
dependencies:
"@octokit/types" "^6.34.0"
deprecation "^2.3.1"
"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.2", "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677"
@@ -4988,6 +5026,16 @@
"@octokit/plugin-request-log" "^1.0.2"
"@octokit/plugin-rest-endpoint-methods" "5.3.1"
"@octokit/rest@^18.12.0":
version "18.12.0"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881"
integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==
dependencies:
"@octokit/core" "^3.5.1"
"@octokit/plugin-paginate-rest" "^2.16.8"
"@octokit/plugin-request-log" "^1.0.4"
"@octokit/plugin-rest-endpoint-methods" "^5.12.0"
"@octokit/types@^5.0.0", "@octokit/types@^5.0.1":
version "5.5.0"
resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b"
@@ -5002,6 +5050,13 @@
dependencies:
"@octokit/openapi-types" "^7.3.2"
"@octokit/types@^6.34.0":
version "6.34.0"
resolved "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218"
integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==
dependencies:
"@octokit/openapi-types" "^11.2.0"
"@octokit/webhooks-methods@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db"
@@ -9898,6 +9953,11 @@ before-after-hook@^2.1.0:
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
before-after-hook@^2.2.0:
version "2.2.2"
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
better-opn@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz#94a55b4695dc79288f31d7d0e5f658320759f7c6"