Skip to content

Commit 3eea6c8

Browse files
chore: modifying snyk container scan function to scan all dockerfiles
1 parent 9bb82ec commit 3eea6c8

File tree

1 file changed

+207
-37
lines changed

1 file changed

+207
-37
lines changed

.github/workflows/snyk-container-scan.yaml

Lines changed: 207 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,62 @@ on:
1313
- '!**.png'
1414

1515
jobs:
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+
- uses: actions/[email protected]
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:
2573
- uses: actions/[email protected]
2674
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

Comments
 (0)