|
1 | 1 | #!/usr/bin/env bash
|
2 | 2 | set -eo pipefail
|
3 | 3 |
|
4 |
| -# Check correct usage |
5 |
| -if [[ ! $2 ]]; then |
6 |
| - echo "Usage: scan-images.sh <os-distribution> <image-tag>" |
7 |
| - exit 2 |
8 |
| -fi |
9 |
| - |
10 |
| -set -u |
| 4 | +# Global variables |
| 5 | +scan_common_args=" \ |
| 6 | + --exit-code 1 \ |
| 7 | + --scanners vuln \ |
| 8 | + --format json \ |
| 9 | + --severity HIGH,CRITICAL \ |
| 10 | + --ignore-unfixed \ |
| 11 | + --db-repository ghcr.io/aquasecurity/trivy-db:2 \ |
| 12 | + --db-repository public.ecr.aws/aquasecurity/trivy-db \ |
| 13 | + --java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \ |
| 14 | + --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db " |
11 | 15 |
|
12 |
| -# Check that trivy is installed |
13 |
| -if ! trivy --version; then |
14 |
| - echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.49.1' |
15 |
| -fi |
16 |
| - |
17 |
| -# Clear any previous outputs |
18 |
| -rm -rf image-scan-output |
| 16 | +# Print usage instructions and error with wrong inputs |
| 17 | +usage() { |
| 18 | + echo "Usage: scan-images.sh <os-distribution> <image-tag> [--sbom]" |
| 19 | + exit 2 |
| 20 | +} |
19 | 21 |
|
20 |
| -# Make fresh output directories |
21 |
| -mkdir -p image-scan-output image-sboms |
| 22 | +# Check dependencies are installed, print installation instructions otherwise |
| 23 | +check_deps_installed() { |
| 24 | + if ! trivy --version > /dev/null; then |
| 25 | + echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.62.1' |
| 26 | + exit 1 |
| 27 | + fi |
| 28 | + if ! yq --version > /dev/null; then |
| 29 | + echo 'Please install yq: sudo dnf/apt install yq' |
| 30 | + exit 1 |
| 31 | + fi |
| 32 | +} |
22 | 33 |
|
23 |
| -# Get built container images |
24 |
| -docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/*:$2" > $1-scanned-container-images.txt |
| 34 | +# Prepare output files |
| 35 | +file_prep() { |
| 36 | + rm -rf image-scan-output |
| 37 | + mkdir -p image-scan-output |
| 38 | + touch image-scan-output/clean-images.txt image-scan-output/dirty-images.txt image-scan-output/critical-images.txt |
| 39 | +} |
25 | 40 |
|
26 |
| -# Make a file of imagename:tag |
27 |
| -images=$(grep --invert-match --no-filename ^REPOSITORY $1-scanned-container-images.txt | sed 's/ \+/:/g' | cut -f 1,2 -d:) |
| 41 | +# Gather image lists |
| 42 | +get_images() { |
| 43 | + docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/*:$2" > $1-scanned-container-images.txt |
| 44 | + grep --invert-match --no-filename ^REPOSITORY $1-scanned-container-images.txt | sed 's/ \+/:/g' | cut -f 1,2 -d: |
| 45 | +} |
28 | 46 |
|
29 |
| -# Ensure output files exist |
30 |
| -touch image-scan-output/clean-images.txt image-scan-output/dirty-images.txt image-scan-output/critical-images.txt |
| 47 | +# Generate ignored vulnerabilities file |
| 48 | +generate_trivy_ignore() { |
| 49 | + local imagename=$1 |
| 50 | + local global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null) |
| 51 | + local image_vulnerabilities=$(yq .$imagename'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null) |
31 | 52 |
|
32 |
| -# If Trivy detects no vulnerabilities, add the image name to clean-images.txt. |
33 |
| -# If there are vulnerabilities detected, add it to dirty-images.txt and |
34 |
| -# generate a csv summary |
35 |
| -# If the image contains at least one critical vulnerabilities, add it to |
36 |
| -# critical-images.txt |
37 |
| -for image in $images; do |
38 |
| - filename=$(basename $image | sed 's/:/\./g') |
39 |
| - imagename=$(echo $filename | cut -d "." -f 1 | sed 's/-/_/g') |
40 |
| - global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml) |
41 |
| - image_vulnerabilities=$(yq .$imagename'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml) |
42 | 53 | touch .trivyignore
|
43 |
| - mkdir -p image-scan-output/$filename |
44 | 54 | for vulnerability in $global_vulnerabilities; do
|
45 | 55 | echo $vulnerability >> .trivyignore
|
46 | 56 | done
|
47 | 57 | for vulnerability in $image_vulnerabilities; do
|
48 | 58 | echo $vulnerability >> .trivyignore
|
49 | 59 | done
|
50 |
| - if $(trivy image \ |
51 |
| - --quiet \ |
52 |
| - --exit-code 1 \ |
53 |
| - --scanners vuln \ |
54 |
| - --format json \ |
55 |
| - --severity HIGH,CRITICAL \ |
56 |
| - --output image-scan-output/${filename}/${filename}.json \ |
57 |
| - --ignore-unfixed \ |
58 |
| - --db-repository ghcr.io/aquasecurity/trivy-db:2 \ |
59 |
| - --db-repository public.ecr.aws/aquasecurity/trivy-db \ |
60 |
| - --java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \ |
61 |
| - --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db \ |
62 |
| - $image); then |
63 |
| - # Clean up the output file for any images with no vulnerabilities |
64 |
| - rm -f image-scan-output/${filename}/${filename}.json |
65 |
| - |
66 |
| - # Add the image to the clean list |
| 60 | +} |
| 61 | + |
| 62 | +# Put results into CSV |
| 63 | +generate_summary_csv() { |
| 64 | + local imagename=$1 |
| 65 | + local filename=$2 |
| 66 | + |
| 67 | + echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${imagename}/${filename}-summary.csv |
| 68 | + |
| 69 | + jq -r '.Results[] |
| 70 | + | select(.Vulnerabilities) |
| 71 | + | .Vulnerabilities |
| 72 | + | map(select(.PkgName | test("kernel") | not )) |
| 73 | + | group_by(.VulnerabilityID) |
| 74 | + | map( |
| 75 | + [ |
| 76 | + (map(.PkgName) | unique | join(";")), |
| 77 | + (map(.PkgPath | select( . != null )) | join(";")), |
| 78 | + .[0].PkgID, |
| 79 | + .[0].VulnerabilityID, |
| 80 | + .[0].FixedVersion, |
| 81 | + .[0].PrimaryURL, |
| 82 | + .[0].Severity |
| 83 | + ] |
| 84 | + ) |
| 85 | + | .[] |
| 86 | + | @csv' image-scan-output/${imagename}/${filename}-scan.json >> image-scan-output/${imagename}/${filename}-summary.csv |
| 87 | +} |
| 88 | + |
| 89 | +# Categorise images based on severity |
| 90 | +categorise_image() { |
| 91 | + local imagename=$1 |
| 92 | + local filename=$2 |
| 93 | + local image=$3 |
| 94 | + |
| 95 | + if [ $(grep "CRITICAL" image-scan-output/${imagename}/${filename}-summary.csv -c) -gt 0 ]; then |
| 96 | + echo "${image}" >> image-scan-output/critical-images.txt |
| 97 | + else |
| 98 | + echo "${image}" >> image-scan-output/high-images.txt |
| 99 | + fi |
| 100 | +} |
| 101 | + |
| 102 | +# Scan images, generate SBOMs if requested |
| 103 | +scan_image() { |
| 104 | + local image=$1 |
| 105 | + local filename=$(basename $image | sed 's/:/\./g') |
| 106 | + local imagename=$(echo $filename | cut -d "." -f 1 | sed 's/-/_/g') |
| 107 | + |
| 108 | + mkdir -p image-scan-output/$imagename |
| 109 | + generate_trivy_ignore $imagename |
| 110 | + |
| 111 | + echo "Scanning $imagename" |
| 112 | + |
| 113 | + # If SBOM is required, generate that first, then generate scan results from it |
| 114 | + if $generate_sbom; then |
| 115 | + trivy image \ |
| 116 | + --format spdx-json \ |
| 117 | + --output image-scan-output/${imagename}/${filename}-sbom.json \ |
| 118 | + $image |
| 119 | + scan_command="trivy sbom $scan_common_args \ |
| 120 | + --output image-scan-output/${imagename}/${filename}-scan.json \ |
| 121 | + image-scan-output/${imagename}/${filename}-sbom.json" |
| 122 | + else |
| 123 | + scan_command="trivy image $scan_common_args \ |
| 124 | + --output image-scan-output/${imagename}/${filename}-scan.json $image" |
| 125 | + fi |
| 126 | + echo "scan command" |
| 127 | + echo "$scan_command" |
| 128 | + # Run scan, against image or SBOM. If no results, delete files. |
| 129 | + if $scan_command; then |
| 130 | + rm -f image-scan-output/${imagename}/${filename}-scan.json |
67 | 131 | echo "${image}" >> image-scan-output/clean-images.txt
|
68 | 132 | else
|
| 133 | + generate_summary_csv $imagename $filename |
| 134 | + categorise_image $imagename $filename $image |
| 135 | + fi |
| 136 | +} |
69 | 137 |
|
70 |
| - # Write a header for the summary CSV |
71 |
| - echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${filename}/${filename}.summary.csv |
72 |
| - |
73 |
| - # Write the summary CSV data |
74 |
| - jq -r '.Results[] |
75 |
| - | select(.Vulnerabilities) |
76 |
| - | .Vulnerabilities |
77 |
| - # Ignore packages with "kernel" in the PkgName |
78 |
| - | map(select(.PkgName | test("kernel") | not )) |
79 |
| - | group_by(.VulnerabilityID) |
80 |
| - | map( |
81 |
| - [ |
82 |
| - (map(.PkgName) | unique | join(";")), |
83 |
| - (map(.PkgPath | select( . != null )) | join(";")), |
84 |
| - .[0].PkgID, |
85 |
| - .[0].VulnerabilityID, |
86 |
| - .[0].FixedVersion, |
87 |
| - .[0].PrimaryURL, |
88 |
| - .[0].Severity |
89 |
| - ] |
90 |
| - ) |
91 |
| - | .[] |
92 |
| - | @csv' image-scan-output/${filename}/${filename}.json >> image-scan-output/${filename}/${filename}.summary.csv |
93 |
| - |
94 |
| - if [ $(grep "CRITICAL" image-scan-output/${filename}/${filename}.summary.csv -c) -gt 0 ]; then |
95 |
| - # If the image contains critical vulnerabilities, add the image to critical list |
96 |
| - echo "${image}" >> image-scan-output/critical-images.txt |
97 |
| - else |
98 |
| - # Otherwise, add the image to the dirty list |
99 |
| - echo "${image}" >> image-scan-output/dirty-images.txt |
100 |
| - fi |
| 138 | +# Main function |
| 139 | +main() { |
| 140 | + if [[ ! $2 ]]; then |
| 141 | + usage |
101 | 142 | fi
|
102 |
| - trivy image \ |
103 |
| - --quiet \ |
104 |
| - --format spdx \ |
105 |
| - --output image-scan-output/${filename}/${filename}-sbom.spdx \ |
106 |
| - --db-repository ghcr.io/aquasecurity/trivy-db:2 \ |
107 |
| - --db-repository public.ecr.aws/aquasecurity/trivy-db \ |
108 |
| - --java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \ |
109 |
| - --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db \ |
110 |
| - $image |
111 |
| -done |
| 143 | + |
| 144 | + generate_sbom=false |
| 145 | + if [[ "$3" == "--sbom" ]]; then |
| 146 | + generate_sbom=true |
| 147 | + fi |
| 148 | + |
| 149 | + set -u |
| 150 | + |
| 151 | + check_deps_installed |
| 152 | + file_prep |
| 153 | + |
| 154 | + images=$(get_images $1 $2) |
| 155 | + for image in $images; do |
| 156 | + scan_image $image |
| 157 | + done |
| 158 | +} |
| 159 | + |
| 160 | +main "$@" |
0 commit comments