1313 - ' !**.png'
1414
1515jobs :
16- build :
16+ find-dockerfiles :
17+ runs-on : ubuntu-latest
18+ timeout-minutes : 5
19+ permissions :
20+ contents : read
21+ outputs :
22+ matrix : ${{ steps.set-matrix.outputs.matrix }}
23+ steps :
24+ 25+ with :
26+ fetch-depth : 0
27+
28+ - name : Find all Dockerfiles (excluding root)
29+ id : find-dockerfiles
30+ run : |
31+ # Find all Dockerfiles except the root one
32+ dockerfiles=$(find . -name "Dockerfile" -not -path "./Dockerfile" -type f | sed 's|^\./||' | jq -R -s -c 'split("\n")[:-1]')
33+ echo "dockerfiles=$dockerfiles" >> $GITHUB_OUTPUT
34+ echo "Found Dockerfiles:"
35+ echo "$dockerfiles" | jq -r '.[]'
36+
37+ - name : Set matrix output
38+ id : set-matrix
39+ run : |
40+ if [ -z "${{ steps.find-dockerfiles.outputs.dockerfiles }}" ] || [ "${{ steps.find-dockerfiles.outputs.dockerfiles }}" == "[]" ]; then
41+ # If no Dockerfiles found, create a dummy matrix entry to prevent job failure
42+ echo 'matrix={"include":[{"dockerfile":"","dockerfile_path":"","dockerfile_name":""}]}' >> $GITHUB_OUTPUT
43+ else
44+ # Create matrix with dockerfile path and sanitized name for image tags
45+ matrix_json=$(echo "${{ steps.find-dockerfiles.outputs.dockerfiles }}" | jq -c '
46+ map({
47+ dockerfile: .,
48+ dockerfile_path: .,
49+ dockerfile_name: (. | gsub("/"; "-") | gsub("_"; "-") | ascii_downcase)
50+ })
51+ ' | jq -c '{include: .}')
52+ echo "matrix<<EOF" >> $GITHUB_OUTPUT
53+ echo "$matrix_json" >> $GITHUB_OUTPUT
54+ echo "EOF" >> $GITHUB_OUTPUT
55+ echo "Generated matrix:"
56+ echo "$matrix_json" | jq .
57+ fi
58+
59+ scan :
60+ needs : find-dockerfiles
61+ if : needs.find-dockerfiles.outputs.matrix != '{"include":[{"dockerfile":"","dockerfile_path":"","dockerfile_name":""}]}' && needs.find-dockerfiles.outputs.matrix != ''
1762 runs-on : ubuntu-latest
1863 concurrency :
1964 group : ${{ github.workflow }}-${{ github.ref }}
2065 cancel-in-progress : ${{ startsWith(github.ref, 'refs/pull/') }}
21- timeout-minutes : 20 # Increased from 10 to handle multi-platform builds
66+ timeout-minutes : 30 # Increased to handle multiple scans
2267 permissions :
2368 contents : read
69+ strategy :
70+ fail-fast : false
71+ matrix : ${{ fromJson(needs.find-dockerfiles.outputs.matrix) }}
2472 steps :
25732674 with :
@@ -52,69 +100,114 @@ jobs:
52100 username : $GITHUB_ACTOR
53101 password : ${{ secrets.ORG_PAT_GITHUB }}
54102
55- # Add Docker Hub login
56103 - name : Login to Docker Hub
57104 uses : docker/login-action@v3
58105 with :
59- username : atlanhq # Replace with your Docker Hub username/organization
106+ username : atlanhq
60107 password : ${{ secrets.DOCKER_HUB_PAT_RW }}
61108
62- # Set up the Snyk CLI
63109 - name : Set up Snyk CLI
64110 uses : snyk/actions/setup@master
65111
112+ - name : Determine Dockerfile context directory
113+ id : dockerfile_context
114+ run : |
115+ dockerfile_path="${{ matrix.dockerfile_path }}"
116+ # Get the directory containing the Dockerfile
117+ context_dir=$(dirname "$dockerfile_path")
118+ if [ "$context_dir" == "." ]; then
119+ context_dir=""
120+ fi
121+ echo "context_dir=$context_dir" >> $GITHUB_OUTPUT
122+ echo "Dockerfile: $dockerfile_path"
123+ echo "Context: ${context_dir:-.}"
124+
66125 - name : Build and load docker image
67- id : ghcr_docker_build_argo
126+ id : docker_build
68127 uses : docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 https://github.com/docker/build-push-action/releases/tag/v6.17.0
69128 with :
70- context : .
71- file : ./Dockerfile
129+ context : ${{ steps.dockerfile_context.outputs.context_dir || '.' }}
130+ file : ./${{ matrix.dockerfile_path }}
72131 push : false
73132 load : true
74133 platforms : linux/amd64
75134 tags : |
76- ghcr.io/atlanhq/${{ github.event.repository.name }}-argo -${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:latest
77- ghcr.io/atlanhq/${{ github.event.repository.name }}-argo -${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:${{ steps.get_version.outputs.version }}
135+ ghcr.io/atlanhq/${{ github.event.repository.name }}-${{ matrix.dockerfile_name }} -${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:latest
136+ ghcr.io/atlanhq/${{ github.event.repository.name }}-${{ matrix.dockerfile_name }} -${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:${{ steps.get_version.outputs.version }}
78137 build-args : |
79138 ACCESS_TOKEN_USR=$GITHUB_ACTOR
80139 ACCESS_TOKEN_PWD=${{ secrets.ORG_PAT_GITHUB }}
81140 env :
82- DOCKER_CLIENT_TIMEOUT : 600 # Increased timeout
141+ DOCKER_CLIENT_TIMEOUT : 600
83142 COMPOSE_HTTP_TIMEOUT : 600
84- # Run the Snyk Docker scan
143+
85144 - name : Run Snyk to check for vulnerabilities
86145 id : snyk_scan
87- continue-on-error : false
146+ continue-on-error : true
88147 env :
89148 SNYK_TOKEN : ${{ secrets.SNYK_TOKEN }}
90149 SNYK_API : https://api.us.snyk.io
91- DOCKER_IMAGE : ghcr.io/atlanhq/${{ github.event.repository.name }}-argo-${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:${{ steps.get_version.outputs.version }}
92- run : ./.github/scripts/run-snyk-scan.sh
150+ DOCKER_IMAGE : ghcr.io/atlanhq/${{ github.event.repository.name }}-${{ matrix.dockerfile_name }}-${{ steps.get_lowercase_branch.outputs.lowercase_branch }}:${{ steps.get_version.outputs.version }}
151+ run : |
152+ # Run Snyk scan and save results
153+ snyk container test $DOCKER_IMAGE --json > snyk_results_${{ matrix.dockerfile_name }}.json || true
154+
155+ # Check if scan failed
156+ if jq -e .error snyk_results_${{ matrix.dockerfile_name }}.json > /dev/null 2>&1; then
157+ echo "scan_failed=true" >> $GITHUB_OUTPUT
158+ echo "error_message=$(jq -r '.error' snyk_results_${{ matrix.dockerfile_name }}.json)" >> $GITHUB_OUTPUT
159+ else
160+ echo "scan_failed=false" >> $GITHUB_OUTPUT
161+ fi
93162
94163 - name : Check Scan Results
95164 id : check_results
96- run : ./.github/scripts/check-scan-results.sh
165+ run : |
166+ result_file="snyk_results_${{ matrix.dockerfile_name }}.json"
167+
168+ if [ ! -f "$result_file" ]; then
169+ echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT
170+ echo "No scan results file found"
171+ exit 0
172+ fi
173+
174+ # Check for high/critical vulnerabilities
175+ OS_HIGH_CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" 2>/dev/null || echo "0")
176+ APP_HIGH_CRITICAL=0
177+ if jq -e '.applications' "$result_file" > /dev/null 2>&1; then
178+ APP_HIGH_CRITICAL=$(jq '[.applications[]?.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" 2>/dev/null || echo "0")
179+ fi
180+ TOTAL_HIGH_CRITICAL=$((OS_HIGH_CRITICAL + APP_HIGH_CRITICAL))
97181
182+ if [ "$TOTAL_HIGH_CRITICAL" -gt 0 ]; then
183+ echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
184+ echo "os_high_critical=$OS_HIGH_CRITICAL" >> $GITHUB_OUTPUT
185+ echo "app_high_critical=$APP_HIGH_CRITICAL" >> $GITHUB_OUTPUT
186+ echo "total_high_critical=$TOTAL_HIGH_CRITICAL" >> $GITHUB_OUTPUT
187+ else
188+ echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT
189+ fi
98190
99- # Create Partner-Friendly Report on Failure
100191 - name : Create Partner-Friendly Report
101192 if : steps.check_results.outputs.vulnerabilities_found == 'true'
102193 id : snyk_report
103194 run : |
104- # Handle cases where the scan itself failed (e.g., auth error)
105- if jq -e .error snyk_results.json > /dev/null; then
106- ERROR_MESSAGE=$(jq -r '.error' snyk_results.json)
107- REPORT="*Snyk scan failed with an error:* ${ERROR_MESSAGE}"
195+ result_file="snyk_results_${{ matrix.dockerfile_name }}.json"
196+
197+ # Handle cases where the scan itself failed
198+ if jq -e .error "$result_file" > /dev/null 2>&1; then
199+ ERROR_MESSAGE=$(jq -r '.error' "$result_file")
200+ REPORT="*Snyk scan failed for ${{ matrix.dockerfile_path }}:* ${ERROR_MESSAGE}"
108201 else
109202 # Get high/critical vulnerability counts
110- OS_HIGH_CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' snyk_results.json )
203+ OS_HIGH_CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" )
111204 APP_HIGH_CRITICAL=0
112- if jq -e '.applications' snyk_results.json > /dev/null; then
113- APP_HIGH_CRITICAL=$(jq '[.applications[]?.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' snyk_results.json )
205+ if jq -e '.applications' "$result_file" > /dev/null; then
206+ APP_HIGH_CRITICAL=$(jq '[.applications[]?.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" )
114207 fi
115208 TOTAL_HIGH_CRITICAL=$((OS_HIGH_CRITICAL + APP_HIGH_CRITICAL))
116209
117- PATH_TO_IMAGE=$(jq -r '.path' snyk_results.json )
210+ PATH_TO_IMAGE=$(jq -r '.path // "'"$DOCKER_IMAGE"'"' "$result_file" )
118211
119212 # Get top 3 UNIQUE critical vulnerabilities with detailed info and path counts
120213 TOP_VULNS=""
@@ -139,7 +232,7 @@ jobs:
139232 " 🔗 ID: \(.id)\n" +
140233 " 🛤️ Paths: \(.pathCount) dependency path(s)\n" +
141234 " ✅ Fixed in: \(if .fixedIn and (.fixedIn | length > 0) then (.fixedIn | join(", ")) else "No fix available" end)"
142- ' snyk_results.json )
235+ ' "$result_file" )
143236 fi
144237
145238 if [ "$OS_HIGH_CRITICAL" -gt 0 ] && [ -z "$TOP_VULNS" ]; then
@@ -163,7 +256,7 @@ jobs:
163256 " 🔗 ID: \(.id)\n" +
164257 " 🛤️ Paths: \(.pathCount) dependency path(s)\n" +
165258 " ✅ Fixed in: \(if .fixedIn and (.fixedIn | length > 0) then (.fixedIn | join(", ")) else "No fix available" end)"
166- ' snyk_results.json )
259+ ' "$result_file" )
167260 fi
168261
169262 # Get unique affected packages for summary
@@ -172,17 +265,18 @@ jobs:
172265 | unique
173266 | .[0:5]
174267 | join(", ")
175- ' snyk_results.json )
268+ ' "$result_file" 2>/dev/null || echo "" )
176269
177270 # Get unique vulnerability count (not total occurrences)
178271 UNIQUE_VULNS=$(jq -r '
179272 [.applications[]?.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")]
180273 | group_by(.id + .moduleName + .version)
181274 | length
182- ' snyk_results.json )
275+ ' "$result_file" 2>/dev/null || echo "0" )
183276
184277 # Build detailed report
185- REPORT="🚨 **High/Critical Vulnerabilities Found in ${PATH_TO_IMAGE}**"$'\n'
278+ REPORT="🚨 **High/Critical Vulnerabilities Found in ${{ matrix.dockerfile_path }}**"$'\n'
279+ REPORT+="📦 **Image:** ${PATH_TO_IMAGE}"$'\n'
186280 REPORT+="📊 **Summary:** ${UNIQUE_VULNS} unique high/critical vulnerabilities (${TOTAL_HIGH_CRITICAL} total occurrences)"$'\n'
187281
188282 if [ "$OS_HIGH_CRITICAL" -gt 0 ]; then
@@ -193,7 +287,7 @@ jobs:
193287 REPORT+="📦 **Application Vulnerabilities:** ${APP_HIGH_CRITICAL} occurrences"$'\n'
194288 fi
195289
196- if [ -n "$AFFECTED_PACKAGES" ]; then
290+ if [ -n "$AFFECTED_PACKAGES" ] && [ "$AFFECTED_PACKAGES" != "" ] ; then
197291 REPORT+="🎯 **Affected Packages:** \`${AFFECTED_PACKAGES}\`"$'\n'
198292 fi
199293
@@ -205,25 +299,101 @@ jobs:
205299 REPORT+="**💡 Next Steps:** Update dependencies to the fixed versions above or choose a different base image."
206300 fi
207301
208- # Set output (no escaping needed for environment variables)
302+ # Set output
209303 {
210304 echo "report_text<<EOF"
211305 echo "$REPORT"
212306 echo "EOF"
307+ echo "dockerfile_path=${{ matrix.dockerfile_path }}" >> $GITHUB_OUTPUT
213308 } >> $GITHUB_OUTPUT
214309
310+ - name : Upload scan results
311+ if : always()
312+ uses : actions/upload-artifact@v4
313+ with :
314+ name : snyk-results-${{ matrix.dockerfile_name }}
315+ path : snyk_results_${{ matrix.dockerfile_name }}.json
316+ retention-days : 7
317+
318+ aggregate-results :
319+ needs : scan
320+ if : always()
321+ runs-on : ubuntu-latest
322+ timeout-minutes : 5
323+ permissions :
324+ contents : read
325+ steps :
326+ - name : Download all scan results
327+ uses : actions/download-artifact@v4
328+ with :
329+ pattern : snyk-results-*
330+ merge-multiple : false
331+
332+ - name : Aggregate vulnerabilities
333+ id : aggregate
334+ run : |
335+ total_vulnerabilities=0
336+ failed_scans=0
337+ all_reports=""
338+
339+ for result_file in snyk-results-*/snyk_results_*.json; do
340+ if [ ! -f "$result_file" ]; then
341+ continue
342+ fi
343+
344+ dockerfile_name=$(basename "$result_file" | sed 's/snyk_results_\(.*\)\.json/\1/')
345+
346+ # Check for errors
347+ if jq -e .error "$result_file" > /dev/null 2>&1; then
348+ failed_scans=$((failed_scans + 1))
349+ continue
350+ fi
351+
352+ # Count vulnerabilities
353+ OS_HIGH_CRITICAL=$(jq '[.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" 2>/dev/null || echo "0")
354+ APP_HIGH_CRITICAL=0
355+ if jq -e '.applications' "$result_file" > /dev/null 2>&1; then
356+ APP_HIGH_CRITICAL=$(jq '[.applications[]?.vulnerabilities[]? | select(.severity == "high" or .severity == "critical")] | length' "$result_file" 2>/dev/null || echo "0")
357+ fi
358+ VULN_COUNT=$((OS_HIGH_CRITICAL + APP_HIGH_CRITICAL))
359+
360+ if [ "$VULN_COUNT" -gt 0 ]; then
361+ total_vulnerabilities=$((total_vulnerabilities + VULN_COUNT))
362+ all_reports+="\n📦 **$dockerfile_name**: $VULN_COUNT high/critical vulnerabilities\n"
363+ fi
364+ done
365+
366+ echo "total_vulnerabilities=$total_vulnerabilities" >> $GITHUB_OUTPUT
367+ echo "failed_scans=$failed_scans" >> $GITHUB_OUTPUT
368+
369+ if [ "$total_vulnerabilities" -gt 0 ] || [ "$failed_scans" -gt 0 ]; then
370+ echo "has_issues=true" >> $GITHUB_OUTPUT
371+ else
372+ echo "has_issues=false" >> $GITHUB_OUTPUT
373+ fi
374+
375+ {
376+ echo "aggregated_report<<EOF"
377+ echo "$all_reports"
378+ echo "EOF"
379+ } >> $GITHUB_OUTPUT
215380
216- # Send Detailed Slack Notification on Failure
217381 - name : Send Slack notification on failure
218- if : steps.check_results .outputs.vulnerabilities_found == 'true'
382+ if : steps.aggregate .outputs.has_issues == 'true'
219383 uses : rtCamp/action-slack-notify@v2
220384 env :
221385 SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }}
222386 SLACK_COLOR : ' danger'
223387 SLACK_MESSAGE : |
224- ${{ steps.snyk_report.outputs.report_text }}
225-
388+ 🚨 **Snyk Security Scan Alert**
389+
390+ **Summary:**
391+ • Total high/critical vulnerabilities found: ${{ steps.aggregate.outputs.total_vulnerabilities }}
392+ • Failed scans: ${{ steps.aggregate.outputs.failed_scans }}
393+
394+ ${{ steps.aggregate.outputs.aggregated_report }}
395+
226396 **🔗 Workflow:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
227- SLACK_TITLE : ' Snyk Security Scan Alert'
397+ SLACK_TITLE : ' Snyk Security Scan Alert - Multiple Images '
228398 SLACK_USERNAME : ' Snyk Security Scanner'
229399 SLACK_ICON_EMOJI : ' :warning:'
0 commit comments