Skip to content

Commit ac5f2d2

Browse files
authored
[DO NOT MERGE YET] New Sigscanner API and full PR check (#26)
* New Sigscanner API and full PR check * Remove commit count limit * simplify * comment
1 parent ac951dd commit ac5f2d2

File tree

1 file changed

+170
-50
lines changed

1 file changed

+170
-50
lines changed
Lines changed: 170 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,190 @@
1-
name: "SigScanner Check"
1+
name: "Sigscanner Check"
2+
description: "This check ensures all commits in a PR have verified signatures"
23

34
on:
45
merge_group:
56
pull_request:
67

7-
permissions: {}
8+
concurrency:
9+
group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number || github.run_id }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
pull-requests: read
814

915
jobs:
1016
sigscanner-check:
1117
runs-on: ubuntu-latest
18+
timeout-minutes: 10
1219
# Skip on merge group events
1320
if: ${{ github.event_name == 'pull_request' }}
21+
env:
22+
REPOSITORY: ${{ github.repository }}
23+
PR_NUMBER: ${{ github.event.pull_request.number }}
24+
VERIFY_MAX_ATTEMPTS: "3"
1425
steps:
15-
- name: "SigScanner checking ${{ github.sha }} by ${{ github.actor }}"
26+
- name: "Fetch PR commits"
27+
id: fetch-commits
1628
env:
17-
API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }}
18-
API_URL: ${{ secrets.SIGSCANNER_API_URL }}
19-
COMMIT_SHA: ${{ github.sha }}
20-
ACTOR: ${{ github.actor }}
21-
REPOSITORY: ${{ github.repository }}
22-
EVENT_NAME: ${{ github.event_name }}
29+
GH_TOKEN: ${{ github.token }}
30+
run: |
31+
# Fetch all commit hashes and their corresponding committers in this PR
32+
gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/commits" --paginate \
33+
--jq '.[] | [.sha, (.committer.login // "")] | join(",")' \
34+
> /tmp/commits_with_committer.csv
35+
36+
commit_count=$(wc -l < /tmp/commits_with_committer.csv | tr -d ' ')
37+
echo "Found $commit_count commits in PR #$PR_NUMBER"
38+
echo "commit-count=$commit_count" >> "$GITHUB_OUTPUT"
2339
40+
if [[ $commit_count -eq 0 ]]; then
41+
echo "❌ Unexpected: no commits to verify"
42+
exit 1
43+
fi
44+
45+
- name: "Sigscanner check"
46+
id: sigscanner
47+
continue-on-error: true
48+
env:
49+
SIGSCANNER_URL: ${{ secrets.SIGSCANNER_URL }}
50+
SIGSCANNER_API_KEY: ${{ secrets.SIGSCANNER_API_KEY }}
51+
COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }}
2452
run: |
25-
echo "🔎 Checking commit $COMMIT_SHA by $ACTOR in $REPOSITORY - $EVENT_NAME"
26-
27-
payload=$(printf '{"commit":"%s","repository":"%s","author":"%s"}' \
28-
"$COMMIT_SHA" "$REPOSITORY" "$ACTOR")
29-
30-
max_attempts=3
31-
attempt=1
32-
33-
# Retry on 5XXs
34-
while [[ $attempt -le $max_attempts ]]; do
35-
echo "Attempt $attempt/$max_attempts"
36-
37-
CODE=$(curl \
38-
--silent \
39-
--output /dev/null \
40-
--write-out '%{http_code}' \
41-
--max-time 20 \
42-
-X POST \
43-
-H "Content-Type: application/json" \
44-
-H "Authorization: $API_TOKEN" \
45-
--url "$API_URL" \
46-
--data "$payload")
47-
48-
echo "Received $CODE"
49-
if [[ "$CODE" == "200" ]]; then
50-
echo "✅ Commit is verified"
51-
exit 0
52-
elif [[ "$CODE" == "400" ]]; then
53-
echo "❌ Bad request"
54-
exit 1
55-
elif [[ "$CODE" == "403" ]]; then
56-
echo "❌ Commit is NOT verified"
57-
exit 1
58-
elif [[ "$CODE" =~ ^5[0-9][0-9]$ ]]; then
59-
if [[ $attempt -lt $max_attempts ]]; then
60-
echo "Retrying in 15s..."
61-
sleep 15
53+
> /tmp/verified_commits.csv
54+
55+
echo "🔎 Verifying $COMMIT_COUNT commits"
56+
57+
# Loop through all the commits
58+
# For each commit, query Sigscanner with retry to check if it's verified
59+
# Verified commit hashes with committer username are saved to /tmp/verified_commits.csv
60+
while IFS=, read -r commit_sha committer_username; do
61+
[[ -z "$commit_sha" ]] && continue
62+
63+
commit_is_verified=false
64+
request_attempt=1
65+
66+
while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do
67+
response=$(curl -s --max-time 20 -G \
68+
-H "X-SIGSCANNER-SECRET: $SIGSCANNER_API_KEY" \
69+
--data-urlencode "commit=$commit_sha" \
70+
--data-urlencode "repository=$REPOSITORY" \
71+
--data-urlencode "author=$committer_username" \
72+
"$SIGSCANNER_URL")
73+
74+
res_verified=$(echo "$response" | jq -r '.verified')
75+
res_error=$(echo "$response" | jq -r '.error')
76+
77+
if [[ "$res_verified" == "true" ]]; then
78+
commit_is_verified=true
79+
break
80+
elif [[ "$res_error" == "null" || "$res_error" == "" ]]; then
81+
# This means the commit is explicitly unverified and shouldn't be retried
82+
break
6283
fi
84+
85+
[[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15
86+
request_attempt=$((request_attempt + 1))
87+
done
88+
89+
if [[ "$commit_is_verified" == "true" ]]; then
90+
echo "✅ $commit_sha"
91+
echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv
6392
else
64-
echo "❌ Unexpected response"
65-
exit 1
93+
echo "❌ $commit_sha"
6694
fi
95+
done < /tmp/commits_with_committer.csv
6796
68-
attempt=$((attempt + 1))
69-
done
97+
verified_commit_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ')
98+
echo "Verified: $verified_commit_count / $COMMIT_COUNT"
99+
100+
if [[ $verified_commit_count -eq $COMMIT_COUNT ]]; then
101+
echo "✅ All commits verified"
102+
exit 0
103+
fi
104+
105+
echo "❌ Not all commits verified"
70106
exit 1
107+
108+
- name: "Sigscanner fallback check"
109+
if: ${{ steps.sigscanner.outcome == 'failure' }}
110+
env:
111+
API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }}
112+
API_URL: ${{ secrets.SIGSCANNER_API_URL }}
113+
COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }}
114+
run: |
115+
touch /tmp/verified_commits.csv
116+
117+
# Extract commits failed to verify earlier by comparing the verified commits file
118+
# with the full list of commits
119+
grep -vxFf /tmp/verified_commits.csv /tmp/commits_with_committer.csv \
120+
> /tmp/pending_commits.csv
121+
122+
pending_commit_count=$(wc -l < /tmp/pending_commits.csv | tr -d ' ')
123+
124+
if [[ $pending_commit_count -eq 0 ]]; then
125+
echo "✅ All commits verified"
126+
exit 0
127+
fi
128+
129+
echo "🔎 Fallback: verifying $pending_commit_count remaining commits"
130+
131+
# Loop through all the commits again with retry with the fallback API
132+
while IFS=, read -r commit_sha committer_username; do
133+
[[ -z "$commit_sha" ]] && continue
134+
135+
commit_is_verified=false
136+
request_attempt=1
137+
138+
while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do
139+
body=$(jq -n \
140+
--arg commit "$commit_sha" \
141+
--arg repository "$REPOSITORY" \
142+
--arg author "$committer_username" \
143+
'{commit: $commit, repository: $repository, author: $author}')
144+
145+
http_status=$(curl --silent --output /dev/null --write-out '%{http_code}' \
146+
--max-time 20 -X POST \
147+
-H "Content-Type: application/json" \
148+
-H "Authorization: $API_TOKEN" \
149+
--url "$API_URL" \
150+
--data "$body")
151+
152+
case $http_status in
153+
200)
154+
commit_is_verified=true
155+
break
156+
;;
157+
400)
158+
echo "❌ $commit_sha - Bad request"
159+
break
160+
;;
161+
403) break ;;
162+
5??)
163+
[[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15
164+
;;
165+
*)
166+
echo "❌ $commit_sha - Unexpected: $http_status"
167+
break
168+
;;
169+
esac
170+
171+
request_attempt=$((request_attempt + 1))
172+
done
173+
174+
if [[ "$commit_is_verified" == "true" ]]; then
175+
echo "✅ $commit_sha"
176+
echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv
177+
else
178+
echo "❌ $commit_sha"
179+
fi
180+
done < /tmp/pending_commits.csv
181+
182+
total_verified_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ')
183+
echo "Verified: $total_verified_count / $COMMIT_COUNT"
184+
185+
if [[ $total_verified_count -ne $COMMIT_COUNT ]]; then
186+
echo "❌ Not all commits verified by fallback"
187+
exit 1
188+
fi
189+
190+
echo "✅ All commits verified"

0 commit comments

Comments
 (0)