5
5
# This workflow:
6
6
# • Builds and tags the container image (`latest` + timestamp)
7
7
# • Re-uses a BuildKit layer cache for faster rebuilds
8
- # • Lints the Dockerfile with Hadolint (CLI) → SARIF
9
- # • Lints the finished image with Dockle (CLI) → SARIF
10
- # • Generates an SPDX SBOM with Syft
11
- # • Scans the image for CRITICAL/HIGH CVEs with Trivy
8
+ # • Lints the Dockerfile with ** Hadolint** (CLI) → SARIF
9
+ # • Lints the finished image with ** Dockle** (CLI) → SARIF
10
+ # • Generates an SPDX SBOM with ** Syft**
11
+ # • Scans the image for CRITICAL/HIGH CVEs with ** Trivy**
12
12
# • Uploads Hadolint, Dockle and Trivy results as SARIF files
13
- # • Pushes the image to GitHub Container Registry (GHCR)
14
- # • Signs & attests the image with Cosign ** key-less ( OIDC)**
13
+ # • Pushes the image to ** GitHub Container Registry (GHCR)**
14
+ # • Signs & attests the image with **Cosign ( key-less OIDC)**
15
15
#
16
16
# Triggers:
17
17
# • Every push / PR to `main`
26
26
pull_request :
27
27
branches : [ "main" ]
28
28
schedule :
29
- - cron : ' 17 18 * * 2' # Tuesday @ 18:17 UTC
29
+ - cron : ' 17 18 * * 2' # Tuesday @ 18:17 UTC
30
30
31
31
# -----------------------------------------------------------------
32
- # GitHub permission scopes for this job
32
+ # Minimal permissions – keep the principle of least privilege
33
33
# -----------------------------------------------------------------
34
34
permissions :
35
- contents : read
36
- packages : write # push to ghcr.io with built-in GITHUB_TOKEN
37
- security-events : write # upload SARIF to “Code-scanning alerts ”
38
- actions : read # needed by upload-sarif in private repos
35
+ contents : read
36
+ packages : write # push to ghcr.io via GITHUB_TOKEN
37
+ security-events : write # upload SARIF to “Code scanning ”
38
+ actions : read # needed by upload-sarif in private repos
39
39
40
40
jobs :
41
41
build-scan-sign :
42
42
runs-on : ubuntu-latest
43
43
44
44
env :
45
- IMAGE_NAME : ghcr.io/${{ github.repository }}
46
- CACHE_DIR : /tmp/.buildx-cache # BuildKit layer cache dir
45
+ CACHE_DIR : /tmp/.buildx-cache # BuildKit layer cache dir
47
46
48
47
steps :
49
48
# -------------------------------------------------------------
@@ -52,19 +51,27 @@ jobs:
52
51
- name : ⬇️ Checkout code
53
52
uses : actions/checkout@v4
54
53
54
+ # -------------------------------------------------------------
55
+ # 0️⃣.5️⃣ Derive lower-case IMAGE_NAME for Docker tag
56
+ # -------------------------------------------------------------
57
+ - name : 🏷️ Set IMAGE_NAME (lower-case repo path)
58
+ run : |
59
+ IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')"
60
+ echo "IMAGE_NAME=$IMAGE" >> "$GITHUB_ENV"
61
+ echo "Will build & push: $IMAGE_NAME"
62
+
55
63
# -------------------------------------------------------------
56
64
# 1️⃣ Dockerfile lint (Hadolint CLI → SARIF)
57
65
# -------------------------------------------------------------
58
- - name : 🔍 Dockerfile lint (Hadolint)
66
+ - name : 🔍 Dockerfile lint (Hadolint)
59
67
id : hadolint
60
- continue-on-error : true # still upload SARIF on failure
68
+ continue-on-error : true
61
69
run : |
62
70
curl -sSL https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 -o /usr/local/bin/hadolint
63
71
chmod +x /usr/local/bin/hadolint
64
72
hadolint -f sarif Containerfile.lite > hadolint-results.sarif
65
73
echo "HADOLINT_EXIT=$?" >> "$GITHUB_ENV"
66
- exit 0 # ensure step reports success
67
-
74
+ exit 0
68
75
- name : ☁️ Upload Hadolint SARIF
69
76
if : always()
70
77
uses : github/codeql-action/upload-sarif@v3
@@ -90,34 +97,32 @@ jobs:
90
97
- name : 🏗️ Build Docker image
91
98
run : |
92
99
TAG=$(date +%s)
93
- echo "TAG=$TAG" >> "$GITHUB_ENV" # reuse in push step
100
+ echo "TAG=$TAG" >> "$GITHUB_ENV"
94
101
docker buildx build \
95
102
--file Containerfile.lite \
96
103
--tag $IMAGE_NAME:$TAG \
97
104
--tag $IMAGE_NAME:latest \
98
105
--cache-from type=local,src=${{ env.CACHE_DIR }} \
99
106
--cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \
100
107
--load \
101
- . # build context is mandatory
108
+ . # build context is mandatory
102
109
103
110
# -------------------------------------------------------------
104
111
# 4️⃣ Image lint (Dockle CLI → SARIF)
105
112
# -------------------------------------------------------------
106
- - name : 🔍 Image lint (Dockle)
113
+ - name : 🔍 Image lint (Dockle)
107
114
id : dockle
108
115
continue-on-error : true
109
116
env :
110
117
DOCKLE_VERSION : 0.4.15
111
118
run : |
112
119
curl -sSL "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.tar.gz" \
113
120
| tar -xz -C /usr/local/bin dockle
114
- dockle --exit-code 1 \
115
- --format sarif \
121
+ dockle --exit-code 1 --format sarif \
116
122
--output dockle-results.sarif \
117
123
$IMAGE_NAME:latest
118
124
echo "DOCKLE_EXIT=$?" >> "$GITHUB_ENV"
119
125
exit 0
120
-
121
126
- name : ☁️ Upload Dockle SARIF
122
127
if : always()
123
128
uses : github/codeql-action/upload-sarif@v3
@@ -127,64 +132,63 @@ jobs:
127
132
# -------------------------------------------------------------
128
133
# 5️⃣ Generate SPDX SBOM with Syft
129
134
# -------------------------------------------------------------
130
- - name : 📄 Generate SBOM (Syft)
135
+ - name : 📄 Generate SBOM (Syft)
131
136
uses : anchore/sbom-action@v0
132
137
with :
133
138
image : ${{ env.IMAGE_NAME }}:latest
134
139
output-file : sbom.spdx.json
135
140
136
141
# -------------------------------------------------------------
137
- # 6️⃣ Trivy CVE scan → SARIF (fails on CRITICAL/HIGH)
142
+ # 6️⃣ Trivy CVE scan → SARIF
138
143
# -------------------------------------------------------------
139
144
- name : 🛡️ Trivy vulnerability scan
140
145
id : trivy
141
- continue-on-error : true # allow SARIF upload
146
+ continue-on-error : true
142
147
uses : aquasecurity/trivy-action@7b7aa264d83dc58691451798b4d117d53d21edfe
143
148
with :
144
149
image-ref : ${{ env.IMAGE_NAME }}:latest
145
150
format : sarif
146
151
output : trivy-results.sarif
147
152
severity : CRITICAL,HIGH
148
- exit-code : 1 # non-zero on vulns
149
-
153
+ exit-code : 1
150
154
- name : ☁️ Upload Trivy SARIF
151
155
if : always()
152
156
uses : github/codeql-action/upload-sarif@v3
153
157
with :
154
158
sarif_file : trivy-results.sarif
155
159
156
160
# -------------------------------------------------------------
157
- # 7️⃣ Push both tags to GHCR (built-in GITHUB_TOKEN)
161
+ # 7️⃣ Push both tags to GHCR
158
162
# -------------------------------------------------------------
159
- - name : 🔑 Log in to GHCR
163
+ - name : 🔑 Log in to GHCR
160
164
uses : docker/login-action@v3
161
165
with :
162
166
registry : ghcr.io
163
167
username : ${{ github.actor }}
164
168
password : ${{ secrets.GITHUB_TOKEN }}
165
169
166
- - name : 🚀 Push image to GHCR
170
+ - name : 🚀 Push image to GHCR
167
171
run : |
168
172
docker push $IMAGE_NAME:${{ env.TAG }}
169
173
docker push $IMAGE_NAME:latest
170
174
171
175
# -------------------------------------------------------------
172
- # 8️⃣ Key-less Cosign sign (OIDC) + provenance
176
+ # 8️⃣ Key-less Cosign sign + attest
173
177
# -------------------------------------------------------------
174
178
- name : 📥 Install Cosign
175
179
uses : sigstore/cosign-installer@v3
176
180
177
- - name : 🔏 Sign & attest image
181
+ - name : 🔏 Sign & attest image
178
182
env :
179
- COSIGN_EXPERIMENTAL : " 1" # enable OIDC flow
183
+ COSIGN_EXPERIMENTAL : " 1" # enable OIDC flow
180
184
run : |
181
185
cosign sign --yes $IMAGE_NAME:latest
182
186
cosign attest --yes --predicate sbom.spdx.json $IMAGE_NAME:latest
183
187
184
188
# -------------------------------------------------------------
185
189
# 9️⃣ Single gate – fail job on any scanner error
186
190
# -------------------------------------------------------------
187
- - name : ⛔ Enforce lint & vuln gates
191
+ - name : ⛔ Enforce lint & vuln gates
188
192
if : |
189
193
env.HADOLINT_EXIT != '0' ||
190
194
env.DOCKLE_EXIT != '0' ||
0 commit comments