Skip to content

Commit 671bf35

Browse files
authored
Migrate from Trivy to Grype for scanning (#321)
* migration to grype * adjustments to the jq for enrichment to ensure naming conventions are maintained * init move from trivy fs scan to grype directory scan * bug fix in upload * add archive sarif step * for filesystem scan, use the new action/scan-filesystem
1 parent 58464b8 commit 671bf35

4 files changed

Lines changed: 370 additions & 27 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
name: Scan Filesystem with Grype SARIF
2+
description: 'Scan filesystem for vulnerabilities and optionally upload the results for GitHub code scanning'
3+
inputs:
4+
path:
5+
description: 'The filesystem path to scan'
6+
required: true
7+
default: '.'
8+
upload-sarif:
9+
description: 'Whether to upload the scan results as a SARIF file'
10+
required: false
11+
default: 'true'
12+
severity-cutoff:
13+
description: 'Minimum severity to report (critical, high, medium, low, negligible)'
14+
required: false
15+
default: 'medium'
16+
fail-build:
17+
description: 'Fail the workflow if vulnerabilities are found'
18+
required: false
19+
default: 'true'
20+
output-file:
21+
description: 'Output file name for SARIF results'
22+
required: false
23+
default: 'results.sarif'
24+
timeout-minutes:
25+
description: 'Maximum time in minutes to wait for the scan to complete'
26+
required: false
27+
default: '30'
28+
retention-days:
29+
description: 'Number of days to retain the scan results artifact'
30+
required: false
31+
default: '90'
32+
category-prefix:
33+
description: 'Prefix to use for the SARIF category name'
34+
required: false
35+
default: 'filesystem-scan-'
36+
only-fixed:
37+
description: 'Only report vulnerabilities that have a fix available'
38+
required: false
39+
default: 'true'
40+
41+
runs:
42+
using: 'composite'
43+
steps:
44+
- name: Scan filesystem with Grype
45+
uses: anchore/scan-action@v6
46+
id: scan
47+
continue-on-error: ${{ inputs.fail-build != 'true' }}
48+
with:
49+
path: "${{ inputs.path }}"
50+
fail-build: "${{ inputs.fail-build }}"
51+
severity-cutoff: "${{ inputs.severity-cutoff }}"
52+
output-format: sarif
53+
output-file: "${{ inputs.output-file }}"
54+
by-cve: true
55+
only-fixed: "${{ inputs.only-fixed }}"
56+
57+
- name: Check scan status
58+
if: steps.scan.outcome == 'failure' && inputs.fail-build == 'true'
59+
shell: bash
60+
run: |
61+
echo "::error::Scan failed for path ${{ inputs.path }}"
62+
echo "Please check the scan logs above for details"
63+
exit 1
64+
65+
- name: Enrich or generate SARIF
66+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
67+
shell: bash
68+
run: |
69+
if [ ! -f ${{ inputs.output-file }} ]; then
70+
echo "No SARIF file found — creating minimal empty SARIF"
71+
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 }}
72+
fi
73+
74+
# Validate SARIF file before enrichment
75+
if ! jq empty ${{ inputs.output-file }}; then
76+
echo "::error::Invalid SARIF file detected"
77+
exit 1
78+
fi
79+
80+
# Create a backup of the original file
81+
cp ${{ inputs.output-file }} ${{ inputs.output-file }}.bak
82+
83+
# Attempt to enrich the SARIF file
84+
if ! jq --arg path "${{ inputs.path }}" \
85+
--arg repo "replicatedhq/ekco" \
86+
--arg scanTime "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
87+
'
88+
.runs[0].properties = {
89+
"scanPath": $path,
90+
"repository": $repo,
91+
"scanTime": $scanTime,
92+
"DisplayName": ["filesystem:", $path] | join(""),
93+
"UniqueID": ["filesystem:", $path] | join(""),
94+
"scanMetadata": {
95+
"path": $path,
96+
"annotations": {
97+
"scanTime": $scanTime,
98+
"tool": "grype",
99+
"toolVersion": "latest"
100+
}
101+
}
102+
}' ${{ inputs.output-file }} > enriched-results.sarif; then
103+
echo "::error::Failed to enrich SARIF file"
104+
# Restore the backup
105+
mv ${{ inputs.output-file }}.bak ${{ inputs.output-file }}
106+
exit 1
107+
fi
108+
109+
# Validate the enriched file
110+
if ! jq empty enriched-results.sarif; then
111+
echo "::error::Invalid enriched SARIF file"
112+
# Restore the backup
113+
mv ${{ inputs.output-file }}.bak ${{ inputs.output-file }}
114+
exit 1
115+
fi
116+
117+
mv enriched-results.sarif ${{ inputs.output-file }}
118+
rm -f ${{ inputs.output-file }}.bak
119+
120+
- name: Upload SARIF file
121+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
122+
uses: github/codeql-action/upload-sarif@v3
123+
with:
124+
sarif_file: ${{ inputs.output-file }}
125+
category: '${{ inputs.category-prefix }}filesystem'
126+
127+
- name: Archive scan results
128+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
129+
uses: actions/upload-artifact@v4
130+
with:
131+
name: "sarif-filesystem"
132+
path: ${{ inputs.output-file }}
133+
retention-days: ${{ inputs.retention-days }}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
name: Scan Container Image Grype SARIF
2+
description: 'Scan a container image for vulnerabilities and optionally upload the results for GitHub code scanning'
3+
inputs:
4+
image-ref:
5+
description: 'The image to scan'
6+
required: true
7+
upload-sarif:
8+
description: 'Whether to upload the scan results as a SARIF file'
9+
required: 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'
39+
40+
runs:
41+
using: 'composite'
42+
steps:
43+
- name: Extract image details
44+
id: image_details
45+
shell: bash
46+
run: |
47+
IMAGE_NAME=$(echo "${{ inputs.image-ref }}" | cut -d':' -f1)
48+
IMAGE_TAG=$(echo "${{ inputs.image-ref }}" | cut -d':' -f2)
49+
[[ "$IMAGE_TAG" == "$IMAGE_NAME" ]] && IMAGE_TAG="latest"
50+
SAFE_NAME=$(echo "${IMAGE_NAME}-${IMAGE_TAG}" | sed 's/[\/:]/-/g')
51+
{
52+
echo "image_name=${IMAGE_NAME}"
53+
echo "image_tag=${IMAGE_TAG}"
54+
echo "safe_name=${SAFE_NAME}"
55+
} >> "$GITHUB_OUTPUT"
56+
57+
- name: Scan image with Grype
58+
uses: anchore/scan-action@v6
59+
id: scan
60+
continue-on-error: ${{ inputs.fail-build != 'true' }}
61+
with:
62+
image: "${{ inputs.image-ref }}"
63+
fail-build: "${{ inputs.fail-build }}"
64+
severity-cutoff: "${{ inputs.severity-cutoff }}"
65+
output-format: sarif
66+
output-file: "${{ inputs.output-file }}"
67+
by-cve: true
68+
only-fixed: "${{ inputs.only-fixed }}"
69+
70+
- name: Check scan status
71+
if: steps.scan.outcome == 'failure' && inputs.fail-build == 'true'
72+
shell: bash
73+
run: |
74+
echo "::error::Scan failed for image ${{ inputs.image-ref }}"
75+
echo "Please check the scan logs above for details"
76+
exit 1
77+
78+
- name: Enrich or generate SARIF
79+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
80+
shell: bash
81+
run: |
82+
if [ ! -f ${{ inputs.output-file }} ]; then
83+
echo "No SARIF file found — creating minimal empty SARIF"
84+
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 }}
85+
fi
86+
87+
# Validate SARIF file before enrichment
88+
if ! jq empty ${{ inputs.output-file }}; then
89+
echo "::error::Invalid SARIF file detected"
90+
exit 1
91+
fi
92+
93+
# Create a backup of the original file
94+
cp ${{ inputs.output-file }} ${{ inputs.output-file }}.bak
95+
96+
# Attempt to enrich the SARIF file
97+
if ! jq --arg imageRef "${{ inputs.image-ref }}" \
98+
--arg repo "replicatedhq/ekco" \
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 fullName "replicatedhq/${{ steps.image_details.outputs.image_name }}" \
103+
'
104+
def strip_registry:
105+
if startswith("docker.io/") then
106+
sub("^docker\\.io/"; "")
107+
else
108+
.
109+
end;
110+
def make_asset_id:
111+
"image:" + (. | strip_registry);
112+
def make_full_ref:
113+
if contains("/") then . else "replicatedhq/" + . end;
114+
.runs[0].properties = {
115+
"imageRef": ($fullName + ":" + $tag),
116+
"repository": $repo,
117+
"scanTime": $scanTime,
118+
"DisplayName": ($fullName + ":" + $tag | make_asset_id),
119+
"UniqueID": ($fullName + ":" + $tag | make_asset_id),
120+
"imageMetadata": {
121+
"name": $fullName,
122+
"tag": $tag,
123+
"annotations": {
124+
"scanTime": $scanTime,
125+
"tool": "grype",
126+
"toolVersion": "latest"
127+
}
128+
}
129+
} |
130+
.runs[0].results[] |= (
131+
if .locations then
132+
.locations[].logicalLocations[].fullyQualifiedName |=
133+
if contains("docker.io/") then
134+
.
135+
else
136+
"docker.io/" + ($fullName + ":" + $tag) +
137+
"@" + (. | split("@")[1])
138+
end
139+
else
140+
.
141+
end
142+
)' ${{ inputs.output-file }} > enriched-results.sarif; then
143+
echo "::error::Failed to enrich SARIF file"
144+
# Restore the backup
145+
mv ${{ inputs.output-file }}.bak ${{ inputs.output-file }}
146+
exit 1
147+
fi
148+
149+
# Validate the enriched file
150+
if ! jq empty enriched-results.sarif; then
151+
echo "::error::Invalid enriched SARIF file"
152+
# Restore the backup
153+
mv ${{ inputs.output-file }}.bak ${{ inputs.output-file }}
154+
exit 1
155+
fi
156+
157+
mv enriched-results.sarif ${{ inputs.output-file }}
158+
rm -f ${{ inputs.output-file }}.bak
159+
160+
- name: Upload SARIF file
161+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
162+
uses: github/codeql-action/upload-sarif@v3
163+
with:
164+
sarif_file: ${{ inputs.output-file }}
165+
category: '${{ inputs.category-prefix }}${{ steps.image_details.outputs.safe_name }}'
166+
167+
- name: Archive scan results
168+
if: ${{ !cancelled() && inputs.upload-sarif == 'true' }}
169+
uses: actions/upload-artifact@v4
170+
with:
171+
name: "sarif-${{ steps.image_details.outputs.safe_name }}"
172+
path: ${{ inputs.output-file }}
173+
retention-days: ${{ inputs.retention-days }}

.github/workflows/scheduled-scan.yaml

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,41 @@ on:
66

77
jobs:
88
scan:
9-
runs-on: ubuntu-22.04
9+
runs-on: ubuntu-latest
1010
steps:
1111
- name: Checkout code
1212
uses: actions/checkout@v4
13-
- name: Run Trivy filesystem vulnerability scanner
13+
- name: Run Grype filesystem vulnerability scanner
1414
id: scan
15-
uses: aquasecurity/trivy-action@0.29.0
15+
uses: ./.github/actions/scan-filesystem
1616
with:
17-
scan-type: 'fs'
18-
format: 'sarif'
19-
output: 'trivy-filesystem-results.sarif'
20-
exit-code: '1'
21-
ignore-unfixed: true
22-
severity: 'CRITICAL,HIGH'
23-
- name: Upload Trivy filesystem scan results to GitHub Security tab
24-
uses: github/codeql-action/upload-sarif@v3
25-
if: always()
26-
with:
27-
sarif_file: 'trivy-filesystem-results.sarif'
17+
path: '.'
18+
fail-build: 'true'
19+
severity-cutoff: 'medium'
20+
output-file: 'grype-filesystem-results.sarif'
21+
only-fixed: 'true'
22+
retention-days: '90'
23+
category-prefix: 'filesystem-scan-'
2824

2925
scan-image:
30-
runs-on: ubuntu-22.04
26+
name: Scan image for vulnerabilities
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
security-events: write
31+
actions: read
3132
steps:
3233
- name: Checkout code
3334
uses: actions/checkout@v4
3435
- name: Build image
3536
run: make docker-image DOCKER_IMAGE=ekco:nightly
36-
- name: Run Trivy image vulnerability scanner
37-
uses: aquasecurity/trivy-action@0.29.0
37+
- name: Run Grype image vulnerability scanner
38+
uses: ./.github/actions/scan-image
3839
with:
40+
category-prefix: 'image-scan'
3941
image-ref: 'ekco:nightly'
40-
format: 'sarif'
41-
exit-code: '1'
42-
ignore-unfixed: true
43-
severity: 'CRITICAL,HIGH'
44-
output: 'trivy-image-results.sarif'
45-
- name: Upload Trivy image scan results to GitHub Security tab
46-
uses: github/codeql-action/upload-sarif@v3
47-
if: always()
48-
with:
49-
sarif_file: 'trivy-image-results.sarif'
42+
only-fixed: 'true'
43+
output-file: 'ekco-image-results.sarif'
44+
retention-days: '90'
45+
severity-cutoff: 'medium'
46+
upload-sarif: 'true'

0 commit comments

Comments
 (0)