Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 170 additions & 50 deletions .github/workflows/sigscanner-check.yml
Original file line number Diff line number Diff line change
@@ -1,70 +1,190 @@
name: "SigScanner Check"
name: "Sigscanner Check"
description: "This check ensures all commits in a PR have verified signatures"

on:
merge_group:
pull_request:

permissions: {}
concurrency:
group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

permissions:
pull-requests: read

jobs:
sigscanner-check:
runs-on: ubuntu-latest
timeout-minutes: 10
# Skip on merge group events
if: ${{ github.event_name == 'pull_request' }}
env:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
VERIFY_MAX_ATTEMPTS: "3"
steps:
- name: "SigScanner checking ${{ github.sha }} by ${{ github.actor }}"
- name: "Fetch PR commits"
id: fetch-commits
env:
API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }}
API_URL: ${{ secrets.SIGSCANNER_API_URL }}
COMMIT_SHA: ${{ github.sha }}
ACTOR: ${{ github.actor }}
REPOSITORY: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
GH_TOKEN: ${{ github.token }}
run: |
# Fetch all commit hashes and their corresponding committers in this PR
gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/commits" --paginate \
--jq '.[] | [.sha, (.committer.login // "")] | join(",")' \
> /tmp/commits_with_committer.csv

commit_count=$(wc -l < /tmp/commits_with_committer.csv | tr -d ' ')
echo "Found $commit_count commits in PR #$PR_NUMBER"
echo "commit-count=$commit_count" >> "$GITHUB_OUTPUT"

if [[ $commit_count -eq 0 ]]; then
echo "❌ Unexpected: no commits to verify"
exit 1
fi

- name: "Sigscanner check"
id: sigscanner
continue-on-error: true
env:
SIGSCANNER_URL: ${{ secrets.SIGSCANNER_URL }}
SIGSCANNER_API_KEY: ${{ secrets.SIGSCANNER_API_KEY }}
COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }}
run: |
echo "🔎 Checking commit $COMMIT_SHA by $ACTOR in $REPOSITORY - $EVENT_NAME"

payload=$(printf '{"commit":"%s","repository":"%s","author":"%s"}' \
"$COMMIT_SHA" "$REPOSITORY" "$ACTOR")

max_attempts=3
attempt=1

# Retry on 5XXs
while [[ $attempt -le $max_attempts ]]; do
echo "Attempt $attempt/$max_attempts"

CODE=$(curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--max-time 20 \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: $API_TOKEN" \
--url "$API_URL" \
--data "$payload")

echo "Received $CODE"
if [[ "$CODE" == "200" ]]; then
echo "✅ Commit is verified"
exit 0
elif [[ "$CODE" == "400" ]]; then
echo "❌ Bad request"
exit 1
elif [[ "$CODE" == "403" ]]; then
echo "❌ Commit is NOT verified"
exit 1
elif [[ "$CODE" =~ ^5[0-9][0-9]$ ]]; then
if [[ $attempt -lt $max_attempts ]]; then
echo "Retrying in 15s..."
sleep 15
> /tmp/verified_commits.csv

echo "🔎 Verifying $COMMIT_COUNT commits"

# Loop through all the commits
# For each commit, query Sigscanner with retry to check if it's verified
# Verified commit hashes with committer username are saved to /tmp/verified_commits.csv
while IFS=, read -r commit_sha committer_username; do
[[ -z "$commit_sha" ]] && continue

commit_is_verified=false
request_attempt=1

while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do
response=$(curl -s --max-time 20 -G \
-H "X-SIGSCANNER-SECRET: $SIGSCANNER_API_KEY" \
--data-urlencode "commit=$commit_sha" \
--data-urlencode "repository=$REPOSITORY" \
--data-urlencode "author=$committer_username" \
"$SIGSCANNER_URL")

res_verified=$(echo "$response" | jq -r '.verified')
res_error=$(echo "$response" | jq -r '.error')

if [[ "$res_verified" == "true" ]]; then
commit_is_verified=true
break
elif [[ "$res_error" == "null" || "$res_error" == "" ]]; then
# This means the commit is explicitly unverified and shouldn't be retried
break
fi

[[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15
request_attempt=$((request_attempt + 1))
done

if [[ "$commit_is_verified" == "true" ]]; then
echo "✅ $commit_sha"
echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv
else
echo "❌ Unexpected response"
exit 1
echo "❌ $commit_sha"
fi
done < /tmp/commits_with_committer.csv

attempt=$((attempt + 1))
done
verified_commit_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ')
echo "Verified: $verified_commit_count / $COMMIT_COUNT"

if [[ $verified_commit_count -eq $COMMIT_COUNT ]]; then
echo "✅ All commits verified"
exit 0
fi

echo "❌ Not all commits verified"
exit 1

- name: "Sigscanner fallback check"
if: ${{ steps.sigscanner.outcome == 'failure' }}
env:
API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }}
API_URL: ${{ secrets.SIGSCANNER_API_URL }}
COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }}
run: |
touch /tmp/verified_commits.csv

# Extract commits failed to verify earlier by comparing the verified commits file
# with the full list of commits
grep -vxFf /tmp/verified_commits.csv /tmp/commits_with_committer.csv \
> /tmp/pending_commits.csv

pending_commit_count=$(wc -l < /tmp/pending_commits.csv | tr -d ' ')

if [[ $pending_commit_count -eq 0 ]]; then
echo "✅ All commits verified"
exit 0
fi

echo "🔎 Fallback: verifying $pending_commit_count remaining commits"

# Loop through all the commits again with retry with the fallback API
while IFS=, read -r commit_sha committer_username; do
[[ -z "$commit_sha" ]] && continue

commit_is_verified=false
request_attempt=1

while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do
body=$(jq -n \
--arg commit "$commit_sha" \
--arg repository "$REPOSITORY" \
--arg author "$committer_username" \
'{commit: $commit, repository: $repository, author: $author}')

http_status=$(curl --silent --output /dev/null --write-out '%{http_code}' \
--max-time 20 -X POST \
-H "Content-Type: application/json" \
-H "Authorization: $API_TOKEN" \
--url "$API_URL" \
--data "$body")

case $http_status in
200)
commit_is_verified=true
break
;;
400)
echo "❌ $commit_sha - Bad request"
break
;;
403) break ;;
5??)
[[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15
;;
*)
echo "❌ $commit_sha - Unexpected: $http_status"
break
;;
esac

request_attempt=$((request_attempt + 1))
done

if [[ "$commit_is_verified" == "true" ]]; then
echo "✅ $commit_sha"
echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv
else
echo "❌ $commit_sha"
fi
done < /tmp/pending_commits.csv

total_verified_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ')
echo "Verified: $total_verified_count / $COMMIT_COUNT"

if [[ $total_verified_count -ne $COMMIT_COUNT ]]; then
echo "❌ Not all commits verified by fallback"
exit 1
fi

echo "✅ All commits verified"
Loading