Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Report release vulnerabilities action #1751

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
29 changes: 29 additions & 0 deletions .github/download-latest-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Copyright The Shipwright Contributors
#
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

# determine the tag of the latest release
releaseTag="$(gh release view --json tagName --jq .tagName)"
echo "[INFO] Tag is ${releaseTag}"
echo "release-tag=${releaseTag}" >>"${GITHUB_OUTPUT}"

# determine the branch name
releaseBranch="release-${releaseTag%.*}"
echo "[INFO] Branch is ${releaseBranch}"
echo "release-branch=${releaseBranch}" >>"${GITHUB_OUTPUT}"

# download the release.yaml
gh release download "${releaseTag}" --clobber --pattern release.yaml --output /tmp/release.yaml
echo "release-yaml=/tmp/release.yaml" >>"${GITHUB_OUTPUT}"

# look at the first image, download the entrypoint to determine the Go version
image="$(grep ghcr.io /tmp/release.yaml | sed -E 's/(image|value)://' | tr -d ' ' | head -n 1)"
entrypoint="$(crane config "${image}" | jq -r '.config.Entrypoint[0]')"
crane export "${image}" - | tar -xf - -C /tmp "${entrypoint}"
goVersion="$(go version "/tmp${entrypoint}" | sed "s#/tmp${entrypoint}: go##")"
goVersion="${goVersion:0:4}"
echo "[INFO] Go version is ${goVersion}"
echo "go-version=${goVersion}" >>"${GITHUB_OUTPUT}"
91 changes: 75 additions & 16 deletions .github/report-release-vulnerabilities.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,59 @@

set -euo pipefail

# download the release.yaml
gh release download --clobber --pattern release.yaml --output /tmp/release.yaml

# extract the images
readarray -t images < <(grep ghcr.io /tmp/release.yaml | sed -E 's/(image|value)://' | tr -d ' ' | sort -u)
readarray -t images < <(grep ghcr.io "${RELEASE_YAML}" | sed -E 's/(image|value)://' | tr -d ' ' | sort -u)

# capture whether vulnerabilities exist
hasVulnerabilities=false
allVulnerabilitiesFixedByRebuild=true

# iterate the images
true>/tmp/report.md
for image in "${images[@]}"; do
echo "[INFO] Checking image ${image}"
echo "## ${image}" >>/tmp/report.md

# Rebuilding image to compare vulnerabilities
entrypoint="$(crane config "${image}" | jq -r '.config.Entrypoint[0]')"
binaryName="$(basename "${entrypoint}")"
echo " [INFO] Rebuilding github.com/shipwright-io/build/cmd/${binaryName}"
pushd "${REPOSITORY}" >/dev/null
KO_DOCKER_REPO=dummy/image ko build "github.com/shipwright-io/build/cmd/${binaryName}" --bare --platform linux/amd64 --push=false --sbom none --tarball /tmp/image.tar
popd >/dev/null

# OS vulnerabilities
echo " [INFO] Checking for OS vulnerabilities"
echo "### OS vulnerabilities" >>/tmp/report.md
osVulns="$(trivy image --format json --ignore-unfixed --no-progress --pkg-types os --scanners vuln --skip-db-update --timeout 10m "${image}")"
osVulnsFound=false
osVulnsLatest="$(trivy image --format json --ignore-unfixed --input /tmp/image.tar --no-progress --pkg-types os --scanners vuln --skip-db-update --timeout 10m)"
while read -r id pkg severity vulnerableVersion fixedVersion; do
if [ "${id}" == "" ]; then
continue
fi

# Check if it exists in the latest image
if [ "$(jq --raw-output "(.Results[0].Vulnerabilities // [])[] | select(.VulnerabilityID == \"${id}\")" <<<"${osVulnsLatest}")" == "" ]; then
fixed=":white_check_mark:"
fixedSentence=" This vulnerability is fixed by a rebuild."
else
fixed=":x:"
fixedSentence=
allVulnerabilitiesFixedByRebuild=false
fi

if [ "${osVulnsFound}" == "false" ]; then
echo "| Vulnerability | Package | Severity | Version |" >>/tmp/report.md
echo "| -- | -- | -- | -- |" >>/tmp/report.md
echo "| Vulnerability | Package | Severity | Version | Fixed by rebuild |" >>/tmp/report.md
echo "| -- | -- | -- | -- | -- |" >>/tmp/report.md
osVulnsFound=true
hasVulnerabilities=true
fi

severityLower="$(tr '[:upper:]' '[:lower:]' <<<"${severity}")"

echo " [INFO] Found ${id} in ${pkg} with severity ${severityLower}. Requires upgrade from ${vulnerableVersion} to ${fixedVersion}."
echo "| ${id} | ${pkg} | ${severityLower} | ${vulnerableVersion} -> ${fixedVersion} |" >>/tmp/report.md
echo " [INFO] Found ${id} in ${pkg} with severity ${severityLower}. Requires upgrade from ${vulnerableVersion} to ${fixedVersion}.${fixedSentence}"
echo "| ${id} | ${pkg} | ${severityLower} | ${vulnerableVersion} -> ${fixedVersion} | ${fixed} |" >>/tmp/report.md
done <<<"$(jq --raw-output '.Results[0].Vulnerabilities[] | [ .VulnerabilityID, .PkgName, .Severity, .InstalledVersion, .FixedVersion ] | @tsv' <<<"${osVulns}")"

if [ "${osVulnsFound}" == "false" ]; then
Expand All @@ -51,24 +68,36 @@ for image in "${images[@]}"; do
# Go vulnerabilities
echo " [INFO] Checking for Go vulnerabilities"
echo "### Go vulnerabilities" >>/tmp/report.md
entrypoint="$(crane config "${image}" | jq -r '.config.Entrypoint[0]')"
crane export "${image}" - | tar -xf - -C /tmp "${entrypoint}"
goVulns="$(govulncheck -format json -mode binary "/tmp${entrypoint}")"
goVulnsFound=false
cat /tmp/image.tar | crane export - - | tar -xf - -C /tmp "${entrypoint}"
goVulnsLatest="$(govulncheck -format json -mode binary "/tmp${entrypoint}")"
rm -f /tmp/image.tar "/tmp${entrypoint}"
while read -r id pkg vulnerableVersion fixedVersion; do
if [ "${id}" == "" ]; then
continue
fi

# Check if it exists in the latest image
if [ "$(jq --raw-output "select(.finding.osv == \"${id}\")" <<<"${goVulnsLatest}")" == "" ]; then
fixed=":white_check_mark:"
fixedSentence=" This vulnerability is fixed by a rebuild."
else
fixed=":x:"
fixedSentence=
allVulnerabilitiesFixedByRebuild=false
fi

if [ "${goVulnsFound}" == "false" ]; then
echo "| Vulnerability | Package | Version |" >>/tmp/report.md
echo "| -- | -- | -- |" >>/tmp/report.md
echo "| Vulnerability | Package | Version | Fixed by rebuild |" >>/tmp/report.md
echo "| -- | -- | -- | -- |" >>/tmp/report.md
goVulnsFound=true
hasVulnerabilities=true
fi

echo " [INFO] Found ${id} in ${pkg}. Requires upgrade from ${vulnerableVersion} to ${fixedVersion}."
echo "| ${id} | ${pkg} | ${vulnerableVersion} -> ${fixedVersion} |" >>/tmp/report.md
echo " [INFO] Found ${id} in ${pkg}. Requires upgrade from ${vulnerableVersion} to ${fixedVersion}.${fixedSentence}"
echo "| ${id} | ${pkg} | ${vulnerableVersion} -> ${fixedVersion} | ${fixed} |" >>/tmp/report.md
done <<<"$(jq --raw-output 'select(.finding != null and .finding.fixed_version != null) | [ .finding.osv, .finding.trace[0].module, .finding.trace[0].version, .finding.fixed_version ] | @tsv' <<<"${goVulns}" | sort -u)"

if [ "${goVulnsFound}" == "false" ]; then
Expand All @@ -81,18 +110,48 @@ done
issues="$(gh issue list --label release-vulnerabilities --json number)"

if [ "$(jq length <<<"${issues}")" == "0" ]; then
assignees="$(dyff json OWNERS | jq -r '.approvers | join(",")')"

if [ "${hasVulnerabilities}" == "true" ]; then
# create new issue
echo "[INFO] Creating new issue"
gh issue create --label release-vulnerabilities --title "Vulnerabilities found in latest release" --body-file /tmp/report.md
gh issue create \
--assignee "${assignees}" \
--label release-vulnerabilities \
--title "Vulnerabilities found in latest release ${RELEASE_TAG}" \
--body-file /tmp/report.md

issues="$(gh issue list --label release-vulnerabilities --json number)"
issueNumber="$(jq '.[0].number' <<<"${issues}")"
fi
else
issueNumber="$(jq '.[0].number' <<<"${issues}")"
if [ "${hasVulnerabilities}" == "true" ]; then
# update issue
echo "[INFO] Updating existing issue ${issueNumber}"
gh issue edit "${issueNumber}" --body-file /tmp/report.md
gh issue edit "${issueNumber}" \
--assignee "${assignees}" \
--body-file /tmp/report.md
else
gh issue close --reason "No vulnerabilities found in the latest release ${RELEASE_TAG}"
fi
fi

# Create release if all vulnerabilities are fixable by a rebuild
if [ "${hasVulnerabilities}" == "true" ] && [ "${allVulnerabilitiesFixedByRebuild}" == "true" ]; then
nextTag="$(semver bump patch "${RELEASE_TAG}")"

# check if tag already exists
if gh release view "${nextTag}" >/dev/null 2>&1; then
echo "[INFO] There is already a new tag ${nextTag} which seemingly was not yet released by a maintainer"
gh issue comment "${issueNumber}" --body "All existing vulnerabilities in ${RELEASE_TAG} can be fixed by a rebuild, but such a rebuild seemingly already exists as ${nextTag}. A maintainer must release this."
else
gh issue close --reason "No vulnerabilities found in the latest release"
echo "[INFO] Triggering build of release ${nextTag} for branch ${RELEASE_BRANCH}"
gh workflow run release.yaml \
--raw-field "git-ref=${RELEASE_BRANCH}" \
--raw-field "tags=${RELEASE_TAG}" \
--raw-field "release=${nextTag}"

gh issue comment "${issueNumber}" --body "Triggered a release build in branch ${RELEASE_BRANCH} for ${RELEASE_TAG}. Please check whether this succeeded. A maintainer must release this."
fi
fi
30 changes: 27 additions & 3 deletions .github/workflows/report-release-vulnerabilities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name: Report release vulnerabilities
on:
schedule:
- cron: '0 0 * * *'
- cron: '15 3 * * *' # 3:15 am UTC = 15 minutes after base image build
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggesting 1 hour delay. Even if the base image build typically only takes ~5 minutes, there's no real urgency to run this so quickly afterwards.

Suggested change
- cron: '15 3 * * *' # 3:15 am UTC = 15 minutes after base image build
- cron: '0 4 * * *' # 4:00 am UTC = 1 hour minutes after base image build

workflow_dispatch: {}
jobs:
report-vulnerabilities:
Expand All @@ -20,8 +20,12 @@ jobs:
check-latest: true
- name: Install crane
run: curl --location --silent "https://github.com/google/go-containerregistry/releases/download/$(curl -s https://api.github.com/repos/google/go-containerregistry/releases/latest | jq -r '.tag_name')/go-containerregistry_$(uname -s)_$(uname -m | sed -e 's/aarch64/arm64/').tar.gz" | sudo tar -xzf - -C /usr/local/bin crane
- name: Install dyff
run: curl --silent --location https://raw.githubusercontent.com/homeport/dyff/main/scripts/download-latest.sh | bash
- name: Install Retry
run: curl --silent --location https://raw.githubusercontent.com/homeport/retry/main/hack/download.sh | bash
run: curl --location --silent https://raw.githubusercontent.com/homeport/retry/main/hack/download.sh | bash
- name: Install semver
run: go install gitlab.com/usvc/utils/semver/cmd/semver@latest
- name: Install Trivy
run: make install-trivy
- name: Update Trivy database
Expand All @@ -31,7 +35,27 @@ jobs:
run: retry trivy image --download-db-only
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run vulnerability check
- name: Download latest release
id: download-latest-release
env:
GH_TOKEN: ${{ github.token }}
run: ./.github/download-latest-release.sh
- name: Checkout release branch
uses: actions/checkout@v4
with:
path: /tmp/release-branch
ref: ${{ steps.download-latest-release.release-branch }}
- name: Install Go version of latest release
uses: actions/setup-go@v5
with:
go-version: "${{ steps.download-latest-release.go-version }}.x"
cache: true
check-latest: true
- name: Report vulnerabilities
env:
GH_TOKEN: ${{ github.token }}
RELEASE_BRANCH: ${{ steps.download-latest-release.release-branch }}
RELEASE_TAG: ${{ steps.download-latest-release.release-tag }}
RELEASE_YAML: ${{ steps.download-latest-release.release-yaml }}
REPOSITORY: /tmp/release-branch
run: ./.github/report-release-vulnerabilities.sh
Loading