-
Notifications
You must be signed in to change notification settings - Fork 2
292 lines (258 loc) · 9.92 KB
/
docker-security-scan.yml
File metadata and controls
292 lines (258 loc) · 9.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
name: Docker Security Scan
on:
push:
branches: [main, develop]
paths:
- "docker/**"
- "templates/docker-compose/**"
- "src/**"
- ".github/workflows/docker-security-scan.yml"
pull_request:
paths:
- "docker/**"
- "templates/docker-compose/**"
- "src/**"
- ".github/workflows/docker-security-scan.yml"
# Scheduled scans are important because new CVEs appear
# even if the code or images didn't change
schedule:
- cron: "0 6 * * *" # Daily at 6 AM UTC
workflow_dispatch:
jobs:
scan-project-images:
name: Scan Project-Built Docker Images
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
strategy:
fail-fast: false
matrix:
image:
- dockerfile: docker/deployer/Dockerfile
context: .
name: deployer
- dockerfile: docker/provisioned-instance/Dockerfile
context: docker/provisioned-instance
name: provisioned-instance
- dockerfile: docker/ssh-server/Dockerfile
context: docker/ssh-server
name: ssh-server
- dockerfile: docker/backup/Dockerfile
context: docker/backup
name: tracker-backup
steps:
- name: Checkout code
uses: actions/checkout@v5
# Build images locally so Trivy scans exactly
# what this repository produces
- name: Build Docker image
run: |
docker build \
-t torrust-tracker-deployer/${{ matrix.image.name }}:latest \
-f ${{ matrix.image.dockerfile }} \
${{ matrix.image.context }}
# Human-readable output in logs
# This NEVER fails the job; it's only for visibility
- name: Display vulnerabilities (table format)
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: torrust-tracker-deployer/${{ matrix.image.name }}:latest
format: "table"
severity: "HIGH,CRITICAL"
exit-code: "0"
# SARIF generation for GitHub Code Scanning
#
# IMPORTANT:
# - exit-code MUST be 0
# - Trivy sometimes exits with 1 even when no vulns exist
# - GitHub Security UI is responsible for enforcement
- name: Generate SARIF (Code Scanning)
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: torrust-tracker-deployer/${{ matrix.image.name }}:latest
format: "sarif"
output: "trivy-${{ matrix.image.name }}.sarif"
severity: "HIGH,CRITICAL"
exit-code: "0"
scanners: "vuln"
- name: Upload SARIF artifact
uses: actions/upload-artifact@v6
if: always()
with:
name: sarif-project-${{ matrix.image.name }}-${{ github.run_id }}
path: trivy-${{ matrix.image.name }}.sarif
retention-days: 30
extract-images:
name: Extract Third-Party Docker Images from Source
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
# JSON array of Docker image references for use in scan matrix
# Example: ["torrust/tracker:develop","mysql:8.4","prom/prometheus:v3.11.2","grafana/grafana:13.0.0","caddy:2.11.2"]
images: ${{ steps.extract.outputs.images }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Build deployer CLI
run: cargo build --release
# Creates a minimal environment config with all optional services
# enabled so that all third-party Docker images appear in the output.
# - MySQL: enables mysql image in docker_images output
# - Prometheus: enables prometheus image in docker_images output
# - Grafana: enables grafana image in docker_images output
# Uses fixture SSH keys (already committed to the repository).
- name: Create environment config for image extraction
run: |
cat > /tmp/ci-images-env.json <<EOF
{
"environment": { "name": "ci-images" },
"ssh_credentials": {
"private_key_path": "$GITHUB_WORKSPACE/fixtures/testing_rsa",
"public_key_path": "$GITHUB_WORKSPACE/fixtures/testing_rsa.pub"
},
"provider": {
"provider": "lxd",
"profile_name": "ci-profile"
},
"tracker": {
"core": {
"database": {
"driver": "mysql",
"host": "mysql",
"port": 3306,
"database_name": "torrust_tracker",
"username": "tracker_user",
"password": "tracker_password"
},
"private": false
},
"udp_trackers": [{ "bind_address": "0.0.0.0:6969" }],
"http_trackers": [{ "bind_address": "0.0.0.0:7070" }],
"http_api": { "bind_address": "0.0.0.0:1212", "admin_token": "ci-token" },
"health_check_api": { "bind_address": "127.0.0.1:1313" }
},
"prometheus": { "scrape_interval_in_secs": 15 },
"grafana": { "admin_user": "admin", "admin_password": "admin" }
}
EOF
- name: Create minimal environment (no infrastructure provisioned)
run: |
./target/release/torrust-tracker-deployer \
--working-dir /tmp/ci-workspace \
create environment \
--env-file /tmp/ci-images-env.json
# Extract Docker images from show command JSON output.
# The show command lists all configured service images in docker_images.
# Caddy is always in the docker-compose stack but is not tracked as
# a domain service, so it is appended to the list manually.
- name: Extract Docker images
id: extract
run: |
show_output=$(./target/release/torrust-tracker-deployer \
--working-dir /tmp/ci-workspace \
show ci-images)
images=$(echo "$show_output" | \
jq -c '[
.docker_images.tracker,
.docker_images.mysql,
.docker_images.prometheus,
.docker_images.grafana
] | map(select(. != null)) + ["caddy:2.11.2"]')
echo "Detected images: $images"
echo "images=$images" >> "$GITHUB_OUTPUT"
scan-third-party-images:
name: Scan Third-Party Docker Images
needs: extract-images
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
# Dynamic image list extracted from the deployer CLI at build time.
# Images come from domain config constants — no manual maintenance needed.
image: ${{ fromJson(needs.extract-images.outputs.images) }}
steps:
- name: Display vulnerabilities (table format)
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: ${{ matrix.image }}
format: "table"
severity: "HIGH,CRITICAL"
exit-code: "0"
# Third-party images should NEVER block CI.
# We only report findings to GitHub Security.
- name: Generate SARIF (Code Scanning)
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: ${{ matrix.image }}
format: "sarif"
output: "trivy.sarif"
severity: "HIGH,CRITICAL"
exit-code: "0"
scanners: "vuln"
# Needed to produce stable artifact names
- name: Sanitize image name
id: sanitize
run: |
echo "name=$(echo '${{ matrix.image }}' | tr '/:' '-')" >> "$GITHUB_OUTPUT"
- name: Upload SARIF artifact
uses: actions/upload-artifact@v6
if: always()
with:
name: sarif-third-party-${{ steps.sanitize.outputs.name }}-${{ github.run_id }}
path: trivy.sarif
retention-days: 30
# Use the supported CodeQL upload action so category tracking works
# for dynamic third-party image configurations.
- name: Upload third-party SARIF
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: trivy.sarif
category: docker-third-party-${{ steps.sanitize.outputs.name }}
continue-on-error: true
upload-sarif-results:
name: Upload SARIF Results to GitHub Security
runs-on: ubuntu-latest
needs:
- scan-project-images
# Always run so we don't lose security visibility
if: always()
permissions:
security-events: write
steps:
- name: Download all SARIF artifacts
uses: actions/download-artifact@v7
with:
pattern: sarif-project-*-${{ github.run_id }}
# Upload each SARIF file with CodeQL Action using unique categories.
# The category parameter enables proper alert tracking per image.
#
# VIEWING RESULTS:
# - For pull requests: /security/code-scanning?query=pr:NUMBER+is:open
# - For branches: /security/code-scanning?query=is:open+branch:BRANCH-NAME
# - For main branch: /security/code-scanning?query=is:open+branch:main (default view)
# The default Security tab filters by "is:open branch:main" which only shows
# alerts from the main branch, not from PR branches.
- name: Upload project provisioned-instance SARIF
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: sarif-project-provisioned-instance-${{ github.run_id }}/trivy-provisioned-instance.sarif
category: docker-project-provisioned-instance
continue-on-error: true
- name: Upload project ssh-server SARIF
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: sarif-project-ssh-server-${{ github.run_id }}/trivy-ssh-server.sarif
category: docker-project-ssh-server
continue-on-error: true