@@ -2,21 +2,24 @@ name: Docker Security Scan
22
33on :
44 push :
5- branches :
6- - main
7- - develop
5+ branches : [main, develop]
86 paths :
97 - " docker/**"
108 - " templates/docker-compose/**"
119 - " .github/workflows/docker-security-scan.yml"
10+
1211 pull_request :
1312 paths :
1413 - " docker/**"
1514 - " templates/docker-compose/**"
1615 - " .github/workflows/docker-security-scan.yml"
16+
17+ # Scheduled scans are important because new CVEs appear
18+ # even if the code or images didn’t change
1719 schedule :
1820 - cron : " 0 6 * * *" # Daily at 6 AM UTC
19- workflow_dispatch : # Allow manual triggering
21+
22+ workflow_dispatch :
2023
2124jobs :
2225 scan-project-images :
2528 timeout-minutes : 15
2629 permissions :
2730 contents : read
31+
2832 strategy :
2933 fail-fast : false
3034 matrix :
@@ -35,42 +39,52 @@ jobs:
3539 - dockerfile : docker/ssh-server/Dockerfile
3640 context : docker/ssh-server
3741 name : ssh-server
42+
3843 steps :
3944 - name : Checkout code
4045 uses : actions/checkout@v4
4146
47+ # Build images locally so Trivy scans exactly
48+ # what this repository produces
4249 - name : Build Docker image
4350 run : |
44- docker build -t torrust-tracker-deployer/${{ matrix.image.name }}:latest \
51+ docker build \
52+ -t torrust-tracker-deployer/${{ matrix.image.name }}:latest \
4553 -f ${{ matrix.image.dockerfile }} \
4654 .
4755
56+ # Human-readable output in logs
57+ # This NEVER fails the job; it’s only for visibility
4858 - name : Display vulnerabilities (table format)
4959 uses :
aquasecurity/[email protected] 5060 with :
5161 image-ref : torrust-tracker-deployer/${{ matrix.image.name }}:latest
5262 format : " table"
5363 severity : " HIGH,CRITICAL"
54- exit-code : " 0" # Don't fail here, just display
64+ exit-code : " 0"
5565
56- - name : Run Trivy vulnerability scanner
66+ # SARIF generation for GitHub Code Scanning
67+ #
68+ # IMPORTANT:
69+ # - exit-code MUST be 0
70+ # - Trivy sometimes exits with 1 even when no vulns exist
71+ # - GitHub Security UI is responsible for enforcement
72+ - name : Generate SARIF (Code Scanning)
5773 uses :
aquasecurity/[email protected] 58- continue-on-error : true
59- id : trivy-scan
6074 with :
6175 image-ref : torrust-tracker-deployer/${{ matrix.image.name }}:latest
6276 format : " sarif"
63- output : " trivy-results- ${{ matrix.image.name }}.sarif"
77+ output : " trivy-${{ matrix.image.name }}.sarif"
6478 severity : " HIGH,CRITICAL"
65- exit-code : " 1 "
66- scanners : " vuln" # Only vulnerabilities, skip secrets (test containers have legitimate SSH keys)
79+ exit-code : " 0 "
80+ scanners : " vuln"
6781
6882 - name : Upload SARIF artifact
6983 uses : actions/upload-artifact@v4
7084 if : always()
7185 with :
7286 name : sarif-project-${{ matrix.image.name }}-${{ github.run_id }}
73- path : " trivy-results- ${{ matrix.image.name }}.sarif"
87+ path : trivy-${{ matrix.image.name }}.sarif
7488 retention-days : 30
7589
7690 scan-third-party-images :
@@ -79,82 +93,88 @@ jobs:
7993 timeout-minutes : 15
8094 permissions :
8195 contents : read
96+
8297 strategy :
8398 fail-fast : false
8499 matrix :
85- # NOTE: These images must match the ones used in templates/ docker-compose/docker-compose.yml.tera
86- # TODO: Automate image detection from docker-compose templates - see https://github.com/torrust/torrust-tracker-deployer/issues/252
100+ # These must match docker-compose templates
101+ # in templates/ docker-compose/docker-compose.yml.tera
87102 image :
88103 - torrust/tracker:develop
89104 - mysql:8.0
90105 - grafana/grafana:11.4.0
91106 - prom/prometheus:v3.0.1
107+
92108 steps :
93109 - name : Display vulnerabilities (table format)
94110 uses :
aquasecurity/[email protected] 95111 with :
96112 image-ref : ${{ matrix.image }}
97113 format : " table"
98114 severity : " HIGH,CRITICAL"
99- exit-code : " 0" # Don't fail here, just display
115+ exit-code : " 0"
100116
101- - name : Run Trivy vulnerability scanner
117+ # Third-party images should NEVER block CI.
118+ # We only report findings to GitHub Security.
119+ - name : Generate SARIF (Code Scanning)
102120 uses :
aquasecurity/[email protected] 103- continue-on-error : true
104- id : trivy-scan
105121 with :
106122 image-ref : ${{ matrix.image }}
107123 format : " sarif"
108- output : " trivy-results .sarif"
124+ output : " trivy.sarif"
109125 severity : " HIGH,CRITICAL"
110- exit-code : " 1 "
111- scanners : " vuln" # Focus on CVEs, not secrets
126+ exit-code : " 0 "
127+ scanners : " vuln"
112128
113- - name : Sanitize image name for artifact
129+ # Needed to produce stable artifact names
130+ - name : Sanitize image name
114131 id : sanitize
115- run : echo "name=$(echo '${{ matrix.image }}' | tr '/:' '-')" >> $GITHUB_OUTPUT
132+ run : |
133+ echo "name=$(echo '${{ matrix.image }}' | tr '/:' '-')" >> "$GITHUB_OUTPUT"
116134
117135 - name : Upload SARIF artifact
118136 uses : actions/upload-artifact@v4
119137 if : always()
120138 with :
121139 name : sarif-third-party-${{ steps.sanitize.outputs.name }}-${{ github.run_id }}
122- path : " trivy-results .sarif"
140+ path : trivy.sarif
123141 retention-days : 30
124142
125143 upload-sarif-results :
126144 name : Upload SARIF Results to GitHub Security
127145 runs-on : ubuntu-latest
128- needs : [scan-project-images, scan-third-party-images]
146+ needs :
147+ - scan-project-images
148+ - scan-third-party-images
149+
150+ # Always run so we don’t lose security visibility
129151 if : always()
152+
130153 permissions :
131154 security-events : write
155+
132156 steps :
133157 - name : Download all SARIF artifacts
134158 uses : actions/download-artifact@v4
135159 with :
136160 pattern : sarif-*-${{ github.run_id }}
137- merge-multiple : false
138161
139- - name : Upload SARIF files to GitHub Security
162+ # We use gh CLI because it’s easier to loop
163+ # and assign stable categories per image
164+ - name : Upload SARIF files
165+ env :
166+ GH_TOKEN : ${{ github.token }}
140167 run : |
141- # Upload each SARIF file with a unique category
142- find . -name "*.sarif" -type f | while read -r sarif_file; do
143- # Extract image name from directory path for category
144- category=$(basename $(dirname "$sarif_file") | sed 's/^sarif-//' | sed 's/-[0-9]*$//')
145- echo "Uploading $sarif_file with category: docker-$category"
146-
147- # Use gh CLI to upload SARIF (simpler than action in loop)
148- cat "$sarif_file" | gh api \
168+ find . -name "*.sarif" -type f | while read -r sarif; do
169+ category=$(basename "$(dirname "$sarif")" | sed 's/^sarif-//' | sed 's/-[0-9]*$//')
170+ echo "Uploading $sarif as docker-$category"
171+
172+ gh api \
149173 --method POST \
150174 -H "Accept: application/vnd.github+json" \
151- -H "X-GitHub-Api-Version: 2022-11-28" \
152175 /repos/${{ github.repository }}/code-scanning/sarifs \
153- -f sarif=@- \
176+ -f sarif=@-"$sarif" \
154177 -f ref="${{ github.ref }}" \
155178 -f commit_sha="${{ github.sha }}" \
156- -f checkout_uri="${{ github.server_url }}/${{ github.repository }}" \
157- -f category="docker-$category" || echo "Failed to upload $sarif_file"
179+ -f category="docker-$category" || echo "Upload failed for $sarif"
158180 done
159- env :
160- GH_TOKEN : ${{ github.token }}
0 commit comments