-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
174 lines (157 loc) · 4.9 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"use strict";
const GithubClient = require("./github");
const SlackClient = require("./slack");
const _ = require("lodash");
const dependabot = require("./dependabotAlerts");
const codeQL = require("./codeqlAlerts");
const codeqlAlerts = require("./codeqlAlerts");
const token = process.env.GITHUB_TOKEN;
const webhook = process.env.SLACK_WEBHOOK;
const searchQuery = process.env.GITHUB_QUERY;
const githubClient = new GithubClient(token);
const slackClient = new SlackClient(webhook);
async function doTheThing() {
let blocks = [
{
type: "section",
text: {
type: "mrkdwn",
text: `:wave: GitHub Security Alerts Report\n\nThe following repositories have open vulnerability alerts and need your attention.`,
},
},
{ type: "divider" },
];
const repos = await githubClient.getRepos(searchQuery);
const codeQLAlerts = await codeQL.getCodeAlerts(repos);
const secretAlerts = await codeQL.getSecretAlerts(repos);
// get enabled and disabled dependabot alerts
const hasAlertsEnabled = await githubClient.hasAlertsEnabled(repos);
const dependabotAlerts = await dependabot.getAlerts(hasAlertsEnabled.enabled);
const results = mergeBlocksByRepo([
...dependabotAlerts,
...secretAlerts,
...codeQLAlerts,
]);
// insert summary blocks
results.forEach((repo) => {
const summaryBlock = getAlertsSummary(repo.repo, repo.summary);
if (summaryBlock) {
blocks.push({ type: "divider" });
repo.blocks.unshift(summaryBlock);
repo.blocks.forEach((block) => {
if (Array.isArray(block)) {
block.forEach((b) => {
blocks.push(b);
});
} else {
blocks.push(block);
}
});
}
});
if (hasAlertsEnabled.disabled.length > 0) {
blocks.push({ type: "divider" });
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `The following do not have alerts enabled: ${hasAlertsEnabled.disabled.join(
", "
)}`,
},
});
}
blocks.push({
type: "context",
elements: [
{
type: "mrkdwn",
text: `Query: ${searchQuery}`,
},
],
});
if (process.env.POST_TO_SLACK === "true") {
const allBlocks = breakBlocks(blocks);
// await will work with oldschool loops, but nothing that requires a callback like array.forEach()
for (let i = 0; i < allBlocks.length; i++) {
await slackClient.postMessage({ blocks: allBlocks[i] });
}
} else {
console.log(`Slack blocks: ${JSON.stringify(blocks, null, 2)}`);
}
}
function mergeBlocksByRepo(zipRepos) {
return zipRepos.reduce(function (zippedRepos, obj) {
const repo = zippedRepos.reduce(function (i, item, j) {
return item.repo === obj.repo ? j : i;
}, -1);
if (repo >= 0) {
zippedRepos[repo].blocks = zippedRepos[repo].blocks.concat(obj.blocks);
zippedRepos[repo].summary = zippedRepos[repo].summary.concat(obj.summary);
} else {
const mergedBlocks = {
repo: obj.repo,
blocks: [obj.blocks],
summary: [obj.summary],
};
zippedRepos = zippedRepos.concat([mergedBlocks]);
}
return zippedRepos;
}, []);
}
function initialRepoSlackBlock(name, alertsSummary) {
if (alertsSummary.length > 0) {
return {
type: "section",
text: {
type: "mrkdwn",
text: `*<https://github.com/sparkpost/${name}|sparkpost/${name}>*\n${alertsSummary.join(
", "
)}\n<https://github.com/sparkpost/${name}/network/alerts|View all>`,
},
accessory: {
type: "image",
image_url:
"https://user-images.githubusercontent.com/10406825/85333522-ba846b80-b4a7-11ea-9774-46fa8ca693a4.png",
alt_text: "github",
},
};
}
}
function getAlertsSummary(name, repoSummary) {
const alertsSummary = [];
repoSummary.forEach((summary) => {
Object.keys(summary).forEach((severity) => {
const count = summary[severity];
if (count > 0) {
alertsSummary.push(`${count} ${severity}`);
}
});
});
return initialRepoSlackBlock(name, alertsSummary);
}
/**
* Slack only allows 50 blocks per message, break array of all blocks into groups of at most 50.
* Returns an array of arrays of blocks in order.
* @param {*} blocks
*/
function breakBlocks(blocks) {
const maxBlocks = 50;
const allBlocks = [];
while (blocks.length > maxBlocks) {
// I'm sorry for using splice, no one is happy about this
// this removes the first maxBlocks array entries from the blocks array and returns them into the chunk array
// we push chunks onto allBlocks until we're below the maxBlocks threshold
const chunk = blocks.splice(0, maxBlocks);
allBlocks.push(chunk);
}
if (blocks.length > 0) {
// add the remaining blocks to the allBlocks array
allBlocks.push(blocks);
}
return allBlocks;
}
return doTheThing().catch((err) => {
console.log(`It failed :( - ${err})`);
process.exit(-1);
});