-
Notifications
You must be signed in to change notification settings - Fork 51
314 lines (256 loc) · 13.7 KB
/
checkAudit.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
# - Github Audit Checker
# - checks if an audit is required
# YES, if:
# > contract in src/*.sol (no test or script contracts)
# - checks if an audit was conducted
# > is there at least one complete entry in the audit log for that contract/version
# - checks if all audit-related files are updated accordingly
# > is the audit report uploaded to ./audit/reports/ ?
# - checks if there is one approving review of an auditor (do we really want this?)
# - checks if the logged audit commit hash is part of the commits of this PULL_REQUEST
name: Audit Check
on:
pull_request:
jobs:
check-audit:
runs-on: ubuntu-latest
env:
auditLogPath: 'audit/auditLog.json'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 ##### Fetch all history for all branches
- name: Check modified files for protected contracts
id: check_eligibility
run: |
##### get all files modified by this PR
FILES=$(git diff --name-only origin/main HEAD)
##### make sure that there are modified files
if [[ -z $FILES ]]; then
echo -e "\033[31mNo files found. This should not happen. Please check the code of the Github action. Aborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
##### Initialize empty variables
PROTECTED_CONTRACTS=""
##### go through all modified file names/paths and identify contracts with path 'src/*'
while IFS= read -r FILE; do
if echo "$FILE" | grep -E '^src/.*\.sol$'; then
##### contract found
PROTECTED_CONTRACTS="${PROTECTED_CONTRACTS}${FILE}"$'\n'
fi
done <<< "$FILES"
##### if none found, exit here as there is nothing to do
if [[ -z "$PROTECTED_CONTRACTS" ]]; then
echo -e "\033[31mNo protected contracts found in files modified/added by this PR.\033[0m"
echo -e "\033[31mNo further checks are required.\033[0m"
# set action output to false
echo "CONTINUE=false" >> $GITHUB_ENV
exit 0
else
##### set action output to true
echo "CONTINUE=true" >> $GITHUB_ENV
fi
echo "PROTECTED_CONTRACTS: $PROTECTED_CONTRACTS"
##### Write filenames to temporary files (using variables here was causing issues due to the file names)
echo -e "$PROTECTED_CONTRACTS" > protected_contracts.txt
- name: Check audit log
id: check-audit-log
if: env.CONTINUE == 'true'
run: |
# load list of protected contracts
PROTECTED_CONTRACTS=$(cat protected_contracts.txt)
# create temp files to store commit hashes and auditor handles
COMMIT_HASHES_FILE="commit_hashes.txt"
AUDITOR_GIT_HANDLES_FILE="auditor_handles.txt"
##### make sure that there are any protected contracts
if [[ -z $PROTECTED_CONTRACTS ]]; then
echo -e "\033[31mNo protected contracts found. This should not happen (action should stop earlier). Please check the code of the Github action. Aborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# iterate through all contracts
while IFS= read -r FILE; do
# load contract version
VERSION=$(sed -nE 's/^\/\/\/ @custom:version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' "$FILE")
##### make sure that contract version was extracted successfully
if [[ -z $VERSION ]]; then
echo -e "\033[31mCould not find version of contract $FILE. This should not happen. Please check the Github action code. Aborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# see if audit log contains an entry with those values
FILENAME=$(basename "$FILE" .sol)
LOG_ENTRIES=$(jq -r --arg filename "$FILENAME" --arg version "$VERSION" '.[$filename][$version][]' "$auditLogPath")
##### make sure that audit log entries were found
if [[ -z $LOG_ENTRIES || "${#LOG_ENTRIES}" -eq 0 ]]; then
echo -e "\033[31mCould not find a logged audit for contract $FILENAME in version $VERSION.\033[0m"
echo -e "\033[31mThis github action cannot complete until the audit log contains a logged audit for this file.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# Iterate through all log entries
echo "$LOG_ENTRIES" | jq -c '.' | while IFS= read -r entry; do
# extract log entry values into variables
AUDIT_COMPLETED_ON=$(echo "$entry" | jq -r '.auditCompletedOn')
AUDITED_BY=$(echo "$entry" | jq -r '.auditedBy')
AUDITOR_GIT_HANDLE=$(echo "$entry" | jq -r '.auditorGitHandle')
AUDIT_REPORT_PATH=$(echo "$entry" | jq -r '.auditReportPath')
AUDIT_COMMIT_HASH=$(echo "$entry" | jq -r '.auditCommitHash')
# make sure that audit log entry contains date
if [ -z "$AUDIT_COMPLETED_ON" ]; then
echo -e "\033[31mThe audit log entry for file $FILE contains an invalid or no 'auditCompletedOn' date.\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit log is complete.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# make sure that audit log entry contains auditor's (company) name
if [ -z "$AUDITED_BY" ]; then
echo -e "\033[31mThe audit log entry for file $FILE contains invalid or no 'auditedBy' information.\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit log is complete.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# make sure that audit log entry contains auditor's git handle
if [ -z "$AUDITOR_GIT_HANDLE" ]; then
echo -e "\033[31mThe audit log entry for file $FILE contains invalid or no 'auditorGitHandle' information.\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit log is complete.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# make sure that audit log entry contains audit report path
if [ -z "$AUDIT_REPORT_PATH" ]; then
echo -e "\033[31mThe audit log entry for file $FILE contains invalid or no 'auditReportPath' information.\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit log is complete.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# make sure that a file exists at the audit report path
if [ ! -f "$AUDIT_REPORT_PATH" ]; then
echo -e "\033[31mCould not find an audit report in path $AUDIT_REPORT_PATH for contract "$FILENAME".\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit report is uploaded to 'audit/reports/'.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# make sure that audit log entry contains audit report path
if [ -z "$AUDIT_COMMIT_HASH" ]; then
echo -e "\033[31mThe audit log entry for file $FILE contains invalid or no 'auditCommitHash' information.\033[0m"
echo -e "\033[31mThis github action cannot complete before the audit log is complete.\033[0m"
echo -e "\033[31mAborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
# store the commit hash in a temporary file to check its validity in a following step
# echo "$AUDIT_COMMIT_HASH" >> "$COMMIT_HASHES_FILE"
echo "$AUDIT_COMMIT_HASH" >> commit_hashes.txt
# store the auditor git handle to check it in a following step if this auditor has reviewed the PR
# echo "$AUDITOR_GIT_HANDLE" >> "$AUDITOR_GIT_HANDLES_FILE"
echo "$AUDITOR_GIT_HANDLE" >> auditor_handles.txt
done
done <<< "$PROTECTED_CONTRACTS"
echo -e "\033[32mAll audit log entries are complete.\033[0m"
# # read the temp files into variables
# AUDIT_COMMIT_HASHES=$(cat "$COMMIT_HASHES_FILE" | tr '\n' ' ')
# AUDITOR_GIT_HANDLES=$(cat "$AUDITOR_GIT_HANDLES_FILE" | tr '\n' ' ')
# echo "AUDIT_COMMIT_HASHES=$AUDIT_COMMIT_HASHES"
# echo "AUDIT_COMMIT_HASHES=$AUDIT_COMMIT_HASHES" >> $GITHUB_ENV
# echo "AUDITOR_GIT_HANDLES=$AUDITOR_GIT_HANDLES"
# echo "AUDITOR_GIT_HANDLES=$AUDITOR_GIT_HANDLES" >> $GITHUB_ENV
# - name: Check if PR is approved by auditor(s)
# id: check-auditor-approval
# uses: actions/github-script@v7
# if: env.CONTINUE == 'true'
# with:
# script: |
# const fs = require('fs');
# const auditorHandlesFile = 'auditor_handles.txt'; // Adjust this if needed
# // Read auditor handles from file
# const auditorHandles = fs.readFileSync(auditorHandlesFile, 'utf-8').split(/\r?\n/).filter(Boolean);
# const { data: reviews } = await github.pulls.listReviews({
# owner: context.repo.owner,
# repo: context.repo.repo,
# pull_number: context.issue.number,
# });
# let allApproved = true;
# auditorHandles.forEach(handle => {
# const approved = reviews.some(review => review.user.login === handle && review.state === 'APPROVED');
# if (!approved) {
# console.log(`PR is not approved by ${handle}`);
# allApproved = false;
# } else {
# console.log(`PR is approved by ${handle}`);
# }
# });
# if (!allApproved) {
# core.setFailed("Not all required auditors have approved the PR.");
# } else {
# core.setOutput('approved', 'true');
# }
- name: Check if all required commits are part of the PR
id: check_commit_hashes
if: env.CONTINUE == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Read commit hashes from file
const commitHashesFile = 'commit_hashes.txt'; // Adjust this if needed
const commitHashes = fs.readFileSync(commitHashesFile, 'utf-8').split(/\r?\n/).filter(Boolean);
// Log the number of commit hashes found
console.log(`${commitHashes.length} commit hashes found`);
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.issue.number;
let allCommitsFound = true;
// define a function that ensures that a given commit hash is part of the current pull request
const checkCommit = async (hash) => {
try {
// get the commit through github REST API
const { data: commit } = await github.rest.repos.getCommit({
owner,
repo,
ref: hash,
});
// get all PRs associated with this commit
const associatedPRs = (await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: hash,
})).data;
// check if any of the associated PR numbers matches with <this> PR number
const isAssociatedWithPR = associatedPRs.some(pr => pr.number === pull_number);
// if current commit is not associated to this PR, end this
if (!isAssociatedWithPR) {
console.error(`None of the associated PRs of commit ${hash} matches with this PR (${pull_number})`);
console.error(`Please check if the 'auditCommitHash' in the audit log is accurate and try again.`);
// set flag to false
allCommitsFound=false;
}
else console.log(`Commit ${hash} is associated with this PR. Check passed.`)
} catch (error) {
console.error(`The following audit commit seems to be invalid: ${hash}`);
console.error(`Please check if the 'auditCommitHash' in the audit log is accurate and try again.`);
// set flag to false
allCommitsFound=false;
}
};
(async () => {
for (const hash of commitHashes) {
console.log(`---------------------------------------------------------------`)
console.log(`Now checking auditCommitHash: ${hash}`)
await checkCommit(hash);
}
// Set environment variable based on whether all commits are found
const envFilePath = process.env.GITHUB_ENV;
fs.appendFileSync(envFilePath, `CONTINUE=${allCommitsFound}\n`);
if (!allCommitsFound)
core.setFailed("This check failed");
})();
core.setOutput('all_commits_present', 'true');
# - name: Assign "Ready_For_PROD_Deployment" label