Skip to content

Commit

Permalink
Improve Report release vulnerabilities action
Browse files Browse the repository at this point in the history
Signed-off-by: Sascha Schwarze <schwarzs@de.ibm.com>
  • Loading branch information
SaschaSchwarze0 committed Dec 13, 2024
1 parent 9432132 commit 172991d
Showing 3 changed files with 131 additions and 19 deletions.
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
@@ -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
@@ -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
@@ -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
@@ -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
workflow_dispatch: {}
jobs:
report-vulnerabilities:
@@ -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
@@ -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

0 comments on commit 172991d

Please sign in to comment.