1- name : Scan image
1+ name : Scan Container Image Grype SARIF
22description : ' Scan a container image for vulnerabilities and optionally upload the results for GitHub code scanning'
33inputs :
44 image-ref :
77 upload-sarif :
88 description : ' Whether to upload the scan results as a SARIF file'
99 required : false
10- default : ' false'
10+ default : ' true'
11+ severity-cutoff :
12+ description : ' Minimum severity to report (critical, high, medium, low, negligible)'
13+ required : false
14+ default : ' medium'
15+ fail-build :
16+ description : ' Fail the workflow if vulnerabilities are found'
17+ required : false
18+ default : ' true'
19+ output-file :
20+ description : ' Output file name for SARIF results'
21+ required : false
22+ default : ' results.sarif'
23+ timeout-minutes :
24+ description : ' Maximum time in minutes to wait for the scan to complete'
25+ required : false
26+ default : ' 30'
27+ retention-days :
28+ description : ' Number of days to retain the scan results artifact'
29+ required : false
30+ default : ' 90'
31+ category-prefix :
32+ description : ' Prefix to use for the SARIF category name'
33+ required : false
34+ default : ' image-scan-'
35+ only-fixed :
36+ description : ' Only report vulnerabilities that have a fix available'
37+ required : false
38+ default : ' true'
1139
1240runs :
1341 using : composite
@@ -18,29 +46,92 @@ runs:
1846 run : |
1947 image_id=$(${{github.action_path}}/image_id.sh '${{ inputs.image-ref }}')
2048 echo "image_id=$image_id" >> $GITHUB_OUTPUT
49+
50+ - name : Extract image details
51+ id : image_details
52+ shell : bash
53+ run : |
54+ IMAGE_NAME=$(echo "${{ inputs.image-ref }}" | cut -d':' -f1)
55+ IMAGE_TAG=$(echo "${{ inputs.image-ref }}" | cut -d':' -f2 | cut -d'@' -f1)
56+ [[ "$IMAGE_TAG" == "$IMAGE_NAME" ]] && IMAGE_TAG="latest"
57+ SAFE_NAME=$(echo "${IMAGE_NAME}-${IMAGE_TAG}" | sed 's/[\/:]/-/g')
58+ SAFE_IMAGE_NAME=$(echo "${IMAGE_NAME}" | sed 's/[\/:]/-/g')
59+ {
60+ echo "image_name=${IMAGE_NAME}"
61+ echo "image_tag=${IMAGE_TAG}"
62+ echo "safe_name=${SAFE_NAME}"
63+ echo "safe_image_name=${SAFE_IMAGE_NAME}"
2164
22- - name : Scan image
23- uses :
aquasecurity/[email protected] 65+ } >> "$GITHUB_OUTPUT"
66+
67+ - name : Scan image with Grype
68+ uses : anchore/scan-action@v6
69+ id : scan
70+ continue-on-error : ${{ inputs.fail-build != 'true' }}
2471 with :
25- image-ref : ' ${{ inputs.image-ref }}'
26- ignore-unfixed : true
27- severity : CRITICAL,HIGH,MEDIUM
28- exit-code : 1
29- trivyignores : .trivyignore
30-
31- - name : Output sarif
32- uses :
aquasecurity/[email protected] 72+ image : " ${{ inputs.image-ref }}"
73+ fail-build : " ${{ inputs.fail-build }}"
74+ severity-cutoff : " ${{ inputs.severity-cutoff }}"
75+ output-format : sarif
76+ output-file : " ${{ inputs.output-file }}"
77+ by-cve : true
78+ only-fixed : " ${{ inputs.only-fixed }}"
79+
80+ - name : Check scan status
81+ if : steps.scan.outcome == 'failure' && inputs.fail-build == 'true'
82+ shell : bash
83+ run : |
84+ echo "::error::Scan failed for image ${{ inputs.image-ref }}"
85+ echo "Please check the scan logs above for details"
86+ exit 1
87+
88+ - name : Enrich or generate SARIF
3389 if : ${{ !cancelled() && inputs.upload-sarif == 'true' }}
34- with :
35- image-ref : ' ${{ matrix.image }}'
36- format : sarif
37- output : trivy-results.sarif
38- ignore-unfixed : true
39- severity : CRITICAL,HIGH,MEDIUM
90+ shell : bash
91+ run : |
92+ if [ ! -f ${{ inputs.output-file }} ]; then
93+ echo "No SARIF file found — creating minimal empty SARIF"
94+ echo '{"version":"2.1.0","runs":[{"tool":{"driver":{"name":"Anchore Grype","informationUri":"https://github.com/anchore/grype","rules":[]}},"results":[],"properties":{"isFallbackSarif":true}}]}' > ${{ inputs.output-file }}
95+ fi
96+
97+ jq --arg imageRef "${{ inputs.image-ref }}" \
98+ --arg repo "replicatedhq/embedded-cluster" \
99+ --arg name "${{ steps.image_details.outputs.image_name }}" \
100+ --arg tag "${{ steps.image_details.outputs.image_tag }}" \
101+ --arg scanTime "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
102+ --arg digest "$(echo "${{ inputs.image-ref }}" | grep -o 'sha256:[a-f0-9]*' || true)" \
103+ '.runs[0].properties = {
104+ "imageRef": (if ($name | startswith("replicatedhq/embedded-cluster/")) then $name else ($name | sub("proxy\\.replicated\\.com/anonymous/(?:ttl\\.sh/runner/|registry\\.k8s\\.io/|kotsadm/|ttl\\.sh/replicated/|replicated/)"; "replicatedhq/embedded-cluster/")) end + ":" + $tag + (if $digest != "" then "@" + $digest else "" end)),
105+ "repository": $repo,
106+ "scanTime": $scanTime,
107+ "imageMetadata": {
108+ "name": (if ($name | startswith("replicatedhq/embedded-cluster/")) then $name else ($name | sub("proxy\\.replicated\\.com/anonymous/(?:ttl\\.sh/runner/|registry\\.k8s\\.io/|kotsadm/|ttl\\.sh/replicated/|replicated/)"; "replicatedhq/embedded-cluster/")) end),
109+ "tag": $tag,
110+ "digest": ($digest | if . == "" then null else . end),
111+ "repoDigest": (if ($name | startswith("replicatedhq/embedded-cluster/")) then $name else ($name | sub("proxy\\.replicated\\.com/anonymous/(?:ttl\\.sh/runner/|registry\\.k8s\\.io/|kotsadm/|ttl\\.sh/replicated/|replicated/)"; "replicatedhq/embedded-cluster/")) end + "@" + ($digest | if . == "" then null else . end)),
112+ "labels": {},
113+ "annotations": {
114+ "scanTime": $scanTime,
115+ "tool": "grype",
116+ "toolVersion": "latest"
117+ }
118+ }
119+ }' ${{ inputs.output-file }} > enriched-results.sarif
40120
41- - name : Upload sarif
121+ mv enriched-results.sarif ${{ inputs.output-file }}
122+
123+ - name : Upload SARIF file
42124 if : ${{ !cancelled() && inputs.upload-sarif == 'true' }}
43125 uses : github/codeql-action/upload-sarif@v3
44126 with :
45- sarif_file : trivy-results.sarif
46- category : ' image-scan:${{ steps.image-id.outputs.image_id }}'
127+ sarif_file : ${{ inputs.output-file }}
128+ category : ' ${{ inputs.category-prefix }}${{ steps.image_details.outputs.safe_image_name }}'
129+
130+
131+ - name : Archive scan results
132+ if : ${{ !cancelled() && inputs.upload-sarif == 'true' }}
133+ uses : actions/upload-artifact@v4
134+ with :
135+ name : " sarif-${{ steps.image_details.outputs.safe_name }}"
136+ path : ${{ inputs.output-file }}
137+ retention-days : ${{ inputs.retention-days }}
0 commit comments