Skip to content
Draft
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
5 changes: 3 additions & 2 deletions .github/workflows/stackhpc-container-image-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ jobs:
run: |
docker ps

- name: Install Trivy
- name: Install Grype and Syft
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.69.2
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin

- name: Install yq
run: |
Expand Down
5 changes: 3 additions & 2 deletions doc/source/contributor/testing-ci-automation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,9 @@ A Pulp authentication proxy container is deployed on the runner that provides
unauthenticated access to the package repositories in Ark. This avoids leaking
Ark credentials into the built container images.

Once built, images are scanned for vulnerabilities using `Trivy
<https://trivy.dev/>`_. Any critical vulnerabilities will break the build,
Once built, images are scanned for vulnerabilities using `Grype
<https://github.com/anchore/grype>`_ and `Syft
<https://github.com/anchore/syft>`_. Any critical vulnerabilities will break the build,
unless the ``push-dirty`` input is true.

If the ``push`` input is true, images are pushed to Ark, and a `container sync
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
###############################################################################
# Trivy allowed vulnerabilities list
# Grype allowed vulnerabilities list

# Example allowed vulnerabilities file setup
#
Expand Down
111 changes: 51 additions & 60 deletions tools/scan-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@
set -eo pipefail

# Disable telemetry and version check:
# https://github.com/aquasecurity/trivy/discussions/8945
export TRIVY_DISABLE_TELEMETRY=true
export TRIVY_SKIP_VERSION_CHECK=true
export GRYPE_CHECK_FOR_APP_UPDATE=false
export SYFT_CHECK_FOR_APP_UPDATE=false

# Global variables
scan_common_args=" \
--exit-code 1 \
--scanners vuln \
--format json \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--db-repository ghcr.io/aquasecurity/trivy-db:2 \
--db-repository public.ecr.aws/aquasecurity/trivy-db \
--java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \
--java-db-repository public.ecr.aws/aquasecurity/trivy-java-db "
--fail-on high \
--output json \
--only-fixed "

# Print usage instructions and error with wrong inputs
usage() {
Expand All @@ -26,11 +19,15 @@ usage() {

# Check dependencies are installed, print installation instructions otherwise
check_deps_installed() {
if ! trivy --version > /dev/null; then
echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.69.2'
if ! grype --version > /dev/null 2>&1; then
echo 'Please install grype: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin'
exit 1
fi
if ! yq --version > /dev/null; then
if ! syft --version > /dev/null 2>&1; then
echo 'Please install syft: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin'
exit 1
fi
if ! yq --version > /dev/null 2>&1; then
echo 'Please install yq: sudo dnf/apt install yq'
exit 1
fi
Expand All @@ -55,20 +52,20 @@ get_images() {
cat "$output_file"
}

# Generate ignored vulnerabilities file
generate_trivy_ignore() {
# Generate grype configuration file
generate_grype_config() {
local imagename=$1
local global_vulnerabilities
global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null)
global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/grype/allowed-vulnerabilities.yml 2> /dev/null)
local image_vulnerabilities
image_vulnerabilities=$(yq ."$imagename"'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null)
image_vulnerabilities=$(yq ."$imagename"'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/grype/allowed-vulnerabilities.yml 2> /dev/null)

truncate -s 0 .trivyignore # ensure we start from a clean slate
echo "ignore:" > .grype.yaml
Comment thread
Alex-Welsh marked this conversation as resolved.
for vulnerability in $global_vulnerabilities; do
echo "$vulnerability" >> .trivyignore
echo " - vulnerability: $vulnerability" >> .grype.yaml
done
for vulnerability in $image_vulnerabilities; do
echo "$vulnerability" >> .trivyignore
echo " - vulnerability: $vulnerability" >> .grype.yaml
done
}

Expand All @@ -79,20 +76,18 @@ generate_summary_csv() {

echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > "$summary"

jq -r '.Results[]
| select(.Vulnerabilities)
| .Vulnerabilities
| map(select(.PkgName | test("kernel") | not ))
| group_by(.VulnerabilityID)
jq -r '.matches
| map(select(.artifact.name | test("kernel") | not ))
| group_by(.vulnerability.id)
| map(
[
(map(.PkgName) | unique | join(";")),
(map(.PkgPath | select( . != null )) | join(";")),
.[0].PkgID,
.[0].VulnerabilityID,
.[0].FixedVersion,
.[0].PrimaryURL,
.[0].Severity
(map(.artifact.name) | unique | join(";")),
(map(.artifact.locations[]?.path // empty) | unique | join(";")),
.[0].artifact.purl,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When multiple matches are grouped by vulnerability ID, they might correspond to different artifacts (packages). Using .[0].artifact.purl only captures the PURL of the first match in the group, potentially losing information about other affected packages. It is better to aggregate all unique PURLs in the group.

Suggested change
.[0].artifact.purl,
(map(.artifact.purl) | unique | join(";")),

.[0].vulnerability.id,
(.[0].vulnerability.fix.versions | join(";")),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the PURL issue, different packages in the same vulnerability group might have different fixed versions. Additionally, if the fix object is null for a match, .[0].vulnerability.fix.versions will cause a jq error. Aggregating the versions and using the optional chaining operator ? is more robust.

Suggested change
(.[0].vulnerability.fix.versions | join(";")),
(map(.vulnerability.fix.versions[]?) | unique | join(";")),

(.[0].vulnerability.urls | first),
(.[0].vulnerability.severity | ascii_upcase)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If vulnerability.severity is missing or null, ascii_upcase will throw a jq error. Providing a default value ensures the script continues to run even if the scan data is incomplete.

Suggested change
(.[0].vulnerability.severity | ascii_upcase)
(.[0].vulnerability.severity // "unknown" | ascii_upcase)

]
)
| .[]
Expand All @@ -111,34 +106,24 @@ categorise_image() {
fi
}

# Generate SBOM, return correct scan command for SBOM
# Generate SBOM using syft, return correct scan command for SBOM
generate_sbom() {
local sbom="$1"
local scan="$2"
local image="$3"
trivy image \
--debug \
--format spdx-json \
--output "$sbom" \
"$image" &> "$sbom.log"
if [ ! -e "$sbom" ]; then
(
echo "ERROR: trivy image didn't produce the sbom file $sbom for $image" 1>&2
echo "==== trivy log ===="
cat "$sbom.log"
) 1>&2
exit 1
elif grep -q FATAL "$sbom.log"; then
syft "$image" \
-o spdx-json \
> "$sbom" 2> "$sbom.log"

if [ ! -s "$sbom" ]; then
Comment on lines +114 to +118
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since set -e is active, if syft fails with a non-zero exit code, the script will terminate immediately. This prevents the custom error handling and log output starting at line 120 from being executed. Wrapping the command in an if statement or using || allows the script to catch the error and provide the intended diagnostic information.

Suggested change
syft "$image" \
-o spdx-json \
> "$sbom" 2> "$sbom.log"
if [ ! -s "$sbom" ]; then
if ! syft "$image" -o spdx-json > "$sbom" 2> "$sbom.log" || [ ! -s "$sbom" ]; then
(
echo "ERROR: syft failed or didn't produce the sbom file $sbom for $image" 1>&2
echo "==== syft log ===="
cat "$sbom.log"
) 1>&2
exit 1
else

(
echo "ERROR: trivy image encountered a fatal error producing $sbom for $image"
echo "==== trivy log ===="
echo "ERROR: syft didn't produce the sbom file $sbom for $image" 1>&2
echo "==== syft log ===="
cat "$sbom.log"
echo "==== sbom.json ===="
cat "$sbom"
) 1>&2
exit 1
else
echo "trivy sbom $scan_common_args --output $scan $sbom"
echo "grype $sbom $scan_common_args"
fi
}

Expand All @@ -154,27 +139,29 @@ scan_image() {
local summary="image-scan-output/${imagename}/${filename}-summary.csv"

mkdir -p "image-scan-output/$imagename"
generate_trivy_ignore "$imagename"
generate_grype_config "$imagename"

# If SBOM is required, generate it first and scan the results, otherwise we
# scan the image directly.
if $generate_sbom; then
echo "Generating SBOM for $imagename"
scan_command="$(generate_sbom "$sbom" "$scan" "$image")"
else
scan_command="trivy image $scan_common_args --output $scan $image"
scan_command="grype $image $scan_common_args"
fi

# Run scan against image or SBOM, format output. If no results, delete files.
echo "Scanning $imagename for vulnerabilities"
if $scan_command >& "$scan.log"; then
if $scan_command > "$scan" 2> "$scan.log"; then
rm -f "$scan"
echo "${image}" >> image-scan-output/clean-images.txt
elif [ ! -f "$scan" ]; then
# return code is 2 if a vulnerability is found with a severity higher than
# configured
elif [ $? -ne 2 ]; then
(
echo "ERROR: trivy scan encountered an error producing $scan"
echo "ERROR: grype scan encountered an error producing $scan"
echo "Command: $scan_command"
echo "==== trivy log ===="
echo "==== grype log ===="
cat "$scan.log"
if $generate_sbom; then
echo "==== sbom.json ===="
Expand All @@ -184,7 +171,11 @@ scan_image() {
exit 1
else
generate_summary_csv "$scan" "$summary"
categorise_image "$summary" "$image"
if [ "$(tail -n +2 "$summary" | wc -l)" -eq 0 ]; then
echo "${image}" >> image-scan-output/clean-images.txt
else
categorise_image "$summary" "$image"
fi
fi
}

Expand Down
Loading