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
9
- # • Lints the finished image with Dockle (CIS best-practices)
8
+ # • Lints the Dockerfile with Hadolint (CLI) → SARIF
9
+ # • Lints the finished image with Dockle (CLI) → SARIF
10
10
# • Generates an SPDX SBOM with Syft
11
11
# • Scans the image for CRITICAL/HIGH CVEs with Trivy
12
- # • Uploads both Dockle and Trivy results as SARIF files
12
+ # • Uploads Hadolint, Dockle and Trivy results as SARIF files
13
13
# • Pushes the image to GitHub Container Registry (GHCR)
14
- # • Signs and attests the image with Cosign **key-less (OIDC)** –
15
- # no private keys or secrets required
14
+ # • Signs + attests the image with Cosign **key-less (OIDC)**
16
15
#
17
16
# Triggers:
18
17
# • Every push / PR to `main`
19
- # • Weekly scheduled run (Tue 18:17 UTC) to catch newly-disclosed CVEs
18
+ # • Weekly scheduled run (Tue 18:17 UTC) to catch new CVEs
20
19
# ---------------------------------------------------------------
21
20
22
21
name : Secure Docker Build
31
30
32
31
# -----------------------------------------------------------------
33
32
# GitHub permission scopes for this job
34
- # - contents: read → checkout source
35
- # - packages: write → push to GHCR with the builtin GITHUB_TOKEN
36
- # - security-events: write → upload SARIF to “Code-scanning alerts”
37
- # - actions: read → required by upload-sarif in private repos
38
33
# -----------------------------------------------------------------
39
34
permissions :
40
35
contents : read
48
43
49
44
env :
50
45
IMAGE_NAME : ghcr.io/${{ github.repository }}
51
- CACHE_DIR : /tmp/.buildx-cache # local BuildKit layer cache
46
+ CACHE_DIR : /tmp/.buildx-cache # BuildKit layer cache dir
52
47
53
48
steps :
54
49
# -------------------------------------------------------------
@@ -58,20 +53,32 @@ jobs:
58
53
uses : actions/checkout@v4
59
54
60
55
# -------------------------------------------------------------
61
- # 1️⃣ Lint Dockerfile (Hadolint)
56
+ # 1️⃣ Dockerfile lint (Hadolint CLI → SARIF )
62
57
# -------------------------------------------------------------
63
58
- name : 🔍 Dockerfile lint (Hadolint)
64
-
59
+ id : hadolint
60
+ continue-on-error : true # capture result; fail later
61
+ run : |
62
+ set -e
63
+ curl -sSL https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 -o /usr/local/bin/hadolint
64
+ chmod +x /usr/local/bin/hadolint
65
+ # Run lint; exit code >0 on rule violations
66
+ hadolint -f sarif Containerfile.lite > hadolint-results.sarif
67
+ echo "HADOLINT_EXIT=$?" >> "$GITHUB_ENV"
68
+
69
+ - name : ☁️ Upload Hadolint SARIF
70
+ if : always()
71
+ uses : github/codeql-action/upload-sarif@v3
65
72
with :
66
- dockerfile : Containerfile.lite
73
+ sarif_file : hadolint-results.sarif
67
74
68
75
# -------------------------------------------------------------
69
76
# 2️⃣ Set up Buildx & restore cache
70
77
# -------------------------------------------------------------
71
78
- name : 🛠️ Set up Docker Buildx
72
79
uses : docker/setup-buildx-action@v3
73
80
74
- - name : 🔄 Restore BuildKit layer cache
81
+ - name : 🔄 Restore BuildKit layer cache
75
82
uses : actions/cache@v4
76
83
with :
77
84
path : ${{ env.CACHE_DIR }}
@@ -94,17 +101,24 @@ jobs:
94
101
--load
95
102
96
103
# -------------------------------------------------------------
97
- # 4️⃣ Lint image with Dockle
104
+ # 4️⃣ Image lint ( Dockle CLI → SARIF)
98
105
# -------------------------------------------------------------
99
106
- name : 🔍 Image lint (Dockle)
100
- uses : erzz/dockle-action@v2
101
- with :
102
- image : ${{ env.IMAGE_NAME }}:latest
103
- format : sarif
104
- output : dockle-results.sarif
105
- exit-code : 1 # fail on WARN+ (remove to make non-blocking)
106
-
107
- - name : ☁️ Upload Dockle SARIF
107
+ id : dockle
108
+ continue-on-error : true
109
+ env :
110
+ DOCKLE_VERSION : 0.4.15
111
+ run : |
112
+ set -e
113
+ curl -sSL "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.tar.gz" \
114
+ | tar -xz -C /usr/local/bin dockle
115
+ dockle --exit-code 1 \
116
+ --format sarif \
117
+ --output dockle-results.sarif \
118
+ $IMAGE_NAME:latest || true
119
+ echo "DOCKLE_EXIT=$?" >> "$GITHUB_ENV"
120
+
121
+ - name : ☁️ Upload Dockle SARIF
108
122
if : always()
109
123
uses : github/codeql-action/upload-sarif@v3
110
124
with :
@@ -120,7 +134,7 @@ jobs:
120
134
output-file : sbom.spdx.json
121
135
122
136
# -------------------------------------------------------------
123
- # 6️⃣ Trivy CVE scan → SARIF (fails on CRITICAL/HIGH)
137
+ # 6️⃣ Trivy CVE scan → SARIF (fails on CRITICAL/HIGH)
124
138
# -------------------------------------------------------------
125
139
- name : 🛡️ Trivy vulnerability scan
126
140
uses : aquasecurity/trivy-action@7b7aa264d83dc58691451798b4d117d53d21edfe
@@ -130,16 +144,16 @@ jobs:
130
144
template : ' @/contrib/sarif.tpl'
131
145
output : trivy-results.sarif
132
146
severity : CRITICAL,HIGH
133
- exit-code : 1 # break build on CRITICAL/HIGH vulns
147
+ exit-code : 1
134
148
135
- - name : ☁️ Upload Trivy SARIF
149
+ - name : ☁️ Upload Trivy SARIF
136
150
if : always()
137
151
uses : github/codeql-action/upload-sarif@v3
138
152
with :
139
153
sarif_file : trivy-results.sarif
140
154
141
155
# -------------------------------------------------------------
142
- # 7️⃣ Push both tags to GHCR (uses built-in GITHUB_TOKEN)
156
+ # 7️⃣ Push both tags to GHCR (using built-in GITHUB_TOKEN)
143
157
# -------------------------------------------------------------
144
158
- name : 🔑 Log in to GHCR
145
159
uses : docker/login-action@v3
@@ -150,7 +164,6 @@ jobs:
150
164
151
165
- name : 🚀 Push image to GHCR
152
166
run : |
153
- # Grab the timestamp tag we built earlier
154
167
TIMESTAMP_TAG=$(docker images --format '{{.Tag}}' $IMAGE_NAME | grep -v latest)
155
168
docker push $IMAGE_NAME:$TIMESTAMP_TAG
156
169
docker push $IMAGE_NAME:latest
@@ -165,6 +178,15 @@ jobs:
165
178
env :
166
179
COSIGN_EXPERIMENTAL : " 1" # enable key-less OIDC flow
167
180
run : |
168
- # Cosign will interactively fetch an OIDC token from GitHub Actions
169
181
cosign sign --yes $IMAGE_NAME:latest
170
182
cosign attest --yes --predicate sbom.spdx.json $IMAGE_NAME:latest
183
+
184
+ # -------------------------------------------------------------
185
+ # 9️⃣ Fail job if any linter exit codes captured non-zero
186
+ # -------------------------------------------------------------
187
+ - name : ⛔ Enforce lint gates
188
+ if : ${{ env.HADOLINT_EXIT != '0' || env.DOCKLE_EXIT != '0' }}
189
+ run : |
190
+ echo "Hadolint exit: $HADOLINT_EXIT"
191
+ echo "Dockle exit: $DOCKLE_EXIT"
192
+ exit 1
0 commit comments