-
Notifications
You must be signed in to change notification settings - Fork 14
365 lines (314 loc) · 14.3 KB
/
issue-labeled-as-security-scan.yml
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
name: Issue labeled security scan
on:
issues:
types: [labeled, unlabeled]
workflow_dispatch:
inputs:
issue:
description: 'Issue number to work with'
required: true
default: '11'
env:
fork-owner: rajbos-actions-test # where to place the forks
jobs:
find-action-name:
runs-on: ubuntu-latest
if: github.event.label.name == 'security-check' || github.event_name == 'workflow_dispatch'
outputs:
action: ${{ steps.get-action.outputs.action }}
owner: ${{ steps.get-action.outputs.owner }}
name: ${{ steps.get-action.outputs.name }}
request_owner: ${{ steps.get-action.outputs.request_owner }}
request_repo: ${{ steps.get-action.outputs.request_repo }}
request_issue: ${{ steps.get-action.outputs.request_issue }}
steps:
- uses: actions/checkout@v2
- id: dispatch_issue_find
name: Find action from dispatch
run: |
echo "Testing for dispatch event with issue number: ${{ github.event.inputs.issue }}"
issue_number=${{ github.event.inputs.issue }}
if [ "${{ github.event.inputs.issue }}" == "" ]; then
echo "issue number not found in workflow dispatch event"
echo 'Found the issue that triggered this event with number [${{ github.event.issue.number }}]'
echo 'Found the issue title [${{ github.event.issue.title }}]'
else
echo "issue number found: [$issue_number]"
# output a fixed variable
echo "::set-output name=issue_number::${issue_number}"
fi
- uses: actions/github-script@v5
name: Find action from issue
id: get-action
with:
result-encoding: string
script: |
const script = require('./src/find-action-from-issue.js')
const owner = context.repo.owner
const repo = context.repo.repo
let issue_number = context.issue.number
if (issue_number == null) {
// try to load issue number from other step:
console.log(`issue number not found in context, searching for it in workflow dispatch step`)
console.log(`issue number: [${{ steps.dispatch_issue_find.outputs.issue_number }}]`)
issue_number = `${{ steps.dispatch_issue_find.outputs.issue_number }}`
}
let { result, action } = await script({github, owner, repo, issue_number})
let commentBody
if (result === 0) {
commentBody = [
`:robot: Found action from the request in the issue body ✅`,
`\`${action}\``,
`This action will now be checked automatically and the results will be posted back in this issue.`
]
}
else {
commentBody = [
`:robot: Could not find action from the request in the issue body :high_voltage:`,
``,
`Please make sure you have this on a line in the body:`,
`uses: organization/repo`
]
}
// create comment letting the user know the results
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: commentBody.join('\n')
});
return result
get-languages-from-action:
name: Load action code languages
runs-on: ubuntu-latest
outputs:
languages: ${{ steps.analysis.outputs.languages }}
steps:
- uses: ruby/setup-ruby@v1
name: Setup Ruby
with:
# Not needed with a .ruby-version file
ruby-version: 3.0
# runs 'bundle install' and caches installed gems automatically
bundler-cache: true
- run : gem install github-linguist
name: Install linguist gem
- run: git clone https://github.com/rajbos/github-actions-requests.git
name: Clone actions repo
- name: Get linguist analysis
id: analysis
run: |
cd github-actions-requests
linguist=$(github-linguist --json)
echo "Linquist results:"
echo $linguist
echo "::set-output name=languages::$linguist"
# fork the repo so we can run dependabot security scan and a CodeQL scan on the fork
fork-action-test:
runs-on: ubuntu-latest
name: Fork action to test organization
needs: find-action-name
steps:
- uses: actions/checkout@v2
- uses: actions/github-script@v5
name: Check input variables have values
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
// note: owner is now the organization the FORK lives in:
const repo = `${{ needs.find-action-name.outputs.name }}`
const owner = `${{ needs.find-action-name.outputs.owner }}`
const org = `${{ env.fork-owner }}`
if (repo === null || repo === '') {
console.log(`repo not found in the outputs of the find-action-name step`)
console.log(`repo: [${{ needs.find-action-name.outputs.name }}]`)
return 1
}
if (owner === null || owner === '') {
console.log(`owner not found in the outputs of the find-action-name step`)
console.log(`owner: [${{ needs.find-action-name.outputs.repo }}]`)
return 1
}
if (org === null || org === '') {
console.log(`org not found in the outputs of the find-action-name step`)
console.log(`org: [${{ needs.find-action-name.outputs.repo }}]`)
return 1
}
return 0
- name: Fork the action repository to rajbos-actions-test
uses: rajbos-actions/[email protected]
with:
token: ${{ secrets.GH_TOKEN }}
repo: ${{ needs.find-action-name.outputs.name }}
owner: ${{ needs.find-action-name.outputs.owner }}
org: ${{ env.fork-owner }}
# todo: add a new comment to the issue indication the action of forking the repo over to the other org?
# since dependabot alerts and dependency graph is enabled on the organization level, but that will not be enabled on new forks
# we need to enable the features on the new forked repo
enable-dependabot:
# enable dependabot settings on the forked repo
runs-on: ubuntu-latest
needs:
- fork-action-test
- find-action-name
steps:
- uses: actions/github-script@v5
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
// note: owner is now the organization the FORK lives in:
const owner = "${{ env.fork-owner }}"
const repo = "${{ needs.find-action-name.outputs.name }}"
// enable dependabot security updates for the repo
await github.request('PUT /repos/{owner}/{repo}/vulnerability-alerts', {
owner,
repo
})
console.log(`Enabled dependabot on the forked repo ${owner}/${repo}. We don't know when those checks are completed. Use another label to trigger the 'dependabot-alerts' workflow`)
# check if the original repo had:
# 1) a dependabot configuration file
# 2) a CodeQL workflow
# 3) run a scan on the used container, if any is used
check-action-security-setup:
runs-on: ubuntu-latest
needs: find-action-name
steps:
- name: Check-out actions-request repo
uses: actions/checkout@v2
- name: Check-out referenced Action
run: |
git clone https://github.com/${{ needs.find-action-name.outputs.action }} action
- name: Check for .github and workflows folder and run docker scan
id: scan
run: |
chmod +x src/security-scan.sh
src/security-scan.sh
env:
ACTION: ${{ needs.find-action-name.outputs.action }}
- name: Upload Trivy report
uses: actions/upload-artifact@v1
if: steps.scan.outputs.action_uses_docker == 'true'
with:
path: issues
name: trivy-issues.txt
mime-type: application/text
- name: Update comment with results
run: |
chmod +x src/update-issue-comment.sh
src/update-issue-comment.sh
env:
HAS_GITHUB_FOLDER: ${{ steps.scan.outputs.has_github_folder }}
HAS_WORKFLOWS_FOLDER: ${{ steps.scan.outputs.has_workflows_folder }}
HAS_DEPENDABOT_CONFIGURATION: ${{ steps.scan.outputs.has_dependabot_configuration }}
HAS_CODEQL_INIT: ${{ steps.scan.outputs.has_codeql_init }}
WORKFLOW_WITH_CODEQL_INIT: ${{ steps.scan.outputs.workflow_with_codeql_init }}
HAS_CODEQL_ANALYZE: ${{ steps.scan.outputs.has_codeql_analyze }}
WORKFLOW_WITH_CODEQL_ANALYZE: ${{ steps.scan.outputs.workflow_with_codeql_analyze }}
ACTION_USES_DOCKER: ${{ steps.scan.outputs.action_uses_docker }}
HAS_LOW_MEDIUM_ISSUES: ${{ steps.scan.outputs.has_low_medium_issues }}
LOW_MEDIUM_ISSUES: ${{ steps.scan.outputs.low_medium_issues }}
HAS_HIGH_CRITICAL_ISSUES: ${{ steps.scan.outputs.has_high_critical_issues }}
HIGH_CRITICAL_ISSUES: ${{ steps.scan.outputs.high_critical_issues }}
- name: Upload result file as artefact
uses: actions/upload-artifact@v1
with:
name: security-scan-result
path: result.md
# add a CodeQL scan on the forked repo
codeql:
runs-on: ubuntu-latest
needs:
- fork-action-test
- find-action-name
- get-languages-from-action
outputs:
codeql: ${{ steps.get-codeql-results.outputs.codeql }}
codeql_run_id: ${{ steps.CodeQL-inject.outputs.codeql_run_id }}
steps:
# todo: what if the action.yml indicates it runs in a docker image? Then we have not neccesarily a way to run CodeQL on actual CODE
- uses: actions/checkout@v2
- uses: actions/github-script@v5
name: Inject CodeQL workflow into new forked repository
# todo: clear all other workflows first, to prevent them from running
id: CodeQL-inject
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const script = require('./src/inject-codeql.js')
const owner = "${{ env.fork-owner }}"
const repo = "${{ needs.find-action-name.outputs.name }}"
console.log(`Using this repository: [${owner}/${repo}]`)
const languages = `${{ needs.get-languages-from-action.outputs.languages }}`
const lang = JSON.parse(languages)
const { ref, targetPath } = await script({github, owner, repo, languages: lang})
console.log(`Waiting for CodeQL workflow to complete`)
// actually wait for the workflow, by dispatching and loading the run id and wait for it to complete
const startAndWaitScript = require('./src/start-and-wait-codeql.js')
console.log(`Using this repository: [${owner}/${repo}] with path [${targetPath}] and ref [${ref}]`)
const { scanResult, run_id } = await startAndWaitScript({github, owner, repo, path: targetPath, ref})
if (scanResult === 0) {
console.log(`CodeQL workflow with run_id [${run_id}] completed successfully`)
}
else {
// todo: output information so it can be read by the user (issue comment)
console.log(`CodeQL workflow with run_id [${run_id}] failed with exit code ${scanResult}`)
}
console.log(`::set-output name=codeql_run_id::'${run_id}'`)
- uses: actions/github-script@v5
name: Get resuls from CodeQL scan
id: get-codeql-results
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const script = require('./src/get-codeql-results.js')
// note: owner is now the organization the FORK lives in:
const owner = "${{ env.fork-owner }}"
const repo = "${{ needs.find-action-name.outputs.name }}"
const result = await script({github, owner, repo})
console.log(`CodeQL scan completed with result: ${JSON.stringify(result)}`)
const output = JSON.stringify(result)
console.log(`::set-output name=codeql::'${output}'`)
display-results:
needs:
- codeql
- find-action-name
- check-action-security-setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v1
with:
name: security-scan-result
#url: ${{ steps.check-action-security-setup.outputs.security_scan_result }}
path: security-scan-result
- run: npm install fs
- uses: actions/github-script@v5
name: Display results
id: display-results
env:
debug: false
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const fs = require('fs');
const codeql = `${{ needs.codeql.outputs.codeql }}`
console.log(`CodeQL scan substring: [${codeql.substring(1, codeql.length - 1)}]`)
const codeqlResult = JSON.parse(codeql.substring(1, codeql.length - 1))
if (${{ env.debug }}) {
console.log(`CodeQL scan results:`)
console.log(`- url: ${codeqlResult.url}`)
console.log(`- results.count: [${codeqlResult.results_count}]`)
console.log(`- environment: [${codeqlResult.environment}]`)
console.log(`- created_at: [${codeqlResult.created_at}]`)
}
const owner = "${{ needs.find-action-name.outputs.request_owner }}"
const repo = "${{ needs.find-action-name.outputs.request_repo }}"
const issue = "${{ needs.find-action-name.outputs.request_issue }}"
const codeql_run_id = ${{ needs.codeql.outputs.codeql_run_id }}
// build up link to CodeQL workflow execution
const forkedRepoOwner = "${{ env.fork-owner }}"
const forkedRepoName = "${{ needs.find-action-name.outputs.name }}"
const codeql_run_link = `https://github.com/${forkedRepoOwner}/${forkedRepoName}/actions/runs/${codeql_run_id}`
const script = require('./src/combine-results.js')
const securityFile = `/home/runner/work/github-actions-requests/github-actions-requests/security-scan-result/result.md` // todo: read work dir from env var
await script({github, owner, repo, issue_number: issue, codeql_run_link, codeqlResult, securityScanResult: securityFile, fs})