Skip to content

Grype Scan

Grype Scan #71

Workflow file for this run

name: Grype Scan
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 0"
permissions:
contents: read
jobs:
grype-scan:
name: Grype Scan
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write
checks: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Install grype
- uses: anchore/scan-action/download-grype@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
id: download_grype
# Run digestabot
- uses: chainguard-dev/digestabot@afe360aa3b0c29d88844138e8fa0349384398967 # v1.3.1
id: digestabot
with:
token: ${{ secrets.GITHUB_TOKEN }}
title-for-pr: 'Update Digests (+ `grype`)'
commit-message: 'Update Digests (+ `grype`)'
branch-for-pr: grype-scan
labels-for-pr: ''
# Iterate over the updates described by the JSON output of digestabot. Run
# grype.
#
# Construct the comment body and set an output based on whether any of the
# scans have critical/high CVEs
- shell: bash
id: grype_scan
if: ${{ steps.digestabot.outputs.pull_request_number != '' }}
run: |
# Track images with High/Critical findings in this output
failed_images='[]'
# Start the comment body
echo 'body<<EOF' >> "${GITHUB_OUTPUT}"
while read -r item; do
image=$(jq -r '.image + "@" + .updated_digest' <<<$item)
# Store the scan output in a filename derived from the image name. We
# can use this to avoid scanning the same images more than once.
grype_output="/tmp/$(sha256sum <<<"${image}" | awk '{print $1}').json"
# Skip images we've already scanned
if [[ -f "${grype_output}" ]]; then
echo "Skipping because we've already processed ${image}"
continue
fi
# Scan the image, save the results
echo "Scanning ${image}"
${{steps.download_grype.outputs.cmd}} "${image}" --add-cpes-if-none -o json > "${grype_output}"
# This is the emoji we will include next to the image in the comment
# body
emoji=':white_check_mark:'
# If there are any CVEs then make the emoji a little warning sign
any_cves=$(jq -r '.matches // [] | .[] | .vulnerability.id' "${grype_output}")
if [[ -n "${any_cves}" ]]; then
emoji=':warning:'
fi
# If there are Critical or High CVEs then append the image to the
# list of failed ones and make the emoji a no entry sign
critical_or_highs=$(jq -r '.matches // [] | .[] | select((.vulnerability.severity | ascii_downcase) as $sev | $sev == "high" or $sev == "critical") | .vulnerability.id' "${grype_output}")
if [[ -n "${critical_or_highs}" ]]; then
failed_images=$(jq -c --arg image "${image}" '. += [$image]' <<<"${failed_images}")
emoji=':no_entry_sign:'
fi
# Construct the header for the image in the comment body
echo "## ${image%@*} ${emoji}" >> "${GITHUB_OUTPUT}"
echo "\`${image#*@}\`" >> "${GITHUB_OUTPUT}"
# Put the scan results inside a <details> block so it's
# collapsible
echo '' >> "${GITHUB_OUTPUT}"
echo '<details>' >> "${GITHUB_OUTPUT}"
echo '' >> "${GITHUB_OUTPUT}"
# Extract vulnerabilities in table format from the json output. Sort
# by severity.
vulnerabilities=$(jq -r '
.matches // []
| sort_by(
.vulnerability.severity
| if . == "Critical" then 0
elif . == "High" then 1
elif . == "Medium" then 2
elif . == "Low" then 3
else 4 end
)
| .[]
| "|" + .artifact.name + "|" + .artifact.version + "|" + .vulnerability.fix.state + "|" + .vulnerability.id + "|" + .vulnerability.severity + "|"
' "${grype_output}")
# Write vulnerabilities to the comment body if there are any.
if [[ -n "${vulnerabilities}" ]]; then
echo '### Vulnerabilities' >> "${GITHUB_OUTPUT}"
echo '| Name | Version | State | CVE | Severity |' >> "${GITHUB_OUTPUT}"
echo '| ---- | ------- | ----- | --- | -------- |' >> "${GITHUB_OUTPUT}"
echo "${vulnerabilities}" >> "${GITHUB_OUTPUT}"
fi
# Otherwise indicate that there are no CVEs
if [[ -z "${vulnerabilities}" ]]; then
echo 'No vulnerabilities found.' >> "${GITHUB_OUTPUT}"
fi
# Close the details block
echo '' >> "${GITHUB_OUTPUT}"
echo '</details>' >> "${GITHUB_OUTPUT}"
echo '' >> "${GITHUB_OUTPUT}"
done < <(jq -c '.updates // [] | .[]' <<<'${{ steps.digestabot.outputs.json }}')
# Close the body
echo 'EOF' >> "${GITHUB_OUTPUT}"
# Create the failed_images output
echo "failed_images=${failed_images}" >> "${GITHUB_OUTPUT}"
# Find the commit sha, set a status on the commit
- if: ${{ steps.digestabot.outputs.pull_request_number != '' }}
shell: bash
run: |
# Get the commit SHA for the PR
commit_sha=$(gh api repos/${{ github.repository }}/pulls/${{ steps.digestabot.outputs.pull_request_number }} --jq '.head.sha')
# Format the failed images one per line
failed_images=$(jq -r '. | join("\\n")' <<<'${{ steps.grype_scan.outputs.failed_images }}')
# Configure the inputs to the check based on whether we have any failed
# images
conclusion='success'
title='Success'
summary='Found no High/Critical severity findings.'
if [[ -n "${failed_images}" ]]; then
conclusion='failure'
title='Found High/Critical CVEs'
summary="Found High/Critical severity findings for these images:\n${failed_images}"
fi
# Create a failed check run
curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/check-runs \
-d '{
"name": "Grype Scan",
"head_sha": "'"${commit_sha}"'",
"status": "completed",
"conclusion": "'"${conclusion}"'",
"output": {
"title": "'"${title}"'",
"summary": "'"${summary}"'"
}
}'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Find the comment (if it already exists)
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
id: find_comment
if: ${{ steps.digestabot.outputs.pull_request_number != '' }}
with:
issue-number: ${{ steps.digestabot.outputs.pull_request_number }}
comment-author: 'github-actions[bot]'
body-includes: 'Results of `grype` for each image.'
# Create/update comment
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
if: ${{ steps.digestabot.outputs.pull_request_number != '' }}
with:
comment-id: ${{ steps.find_comment.outputs.comment-id }}
issue-number: ${{ steps.digestabot.outputs.pull_request_number }}
body: |
Results of `grype` for each image.
${{ steps.grype_scan.outputs.body }}
token: ${{ secrets.GITHUB_TOKEN }}
edit-mode: replace