Skip to content

Commit ecf0ae5

Browse files
authored
Update docker-image.yml
Added linting and cosign
1 parent 6b49716 commit ecf0ae5

File tree

1 file changed

+159
-7
lines changed

1 file changed

+159
-7
lines changed

.github/workflows/docker-image.yml

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,170 @@
1-
name: Docker Image CI
1+
# ===============================================================
2+
# 📦 Secure Docker Build & Scan Workflow
3+
# ===============================================================
4+
#
5+
# This workflow:
6+
# • Builds and tags the container image (`latest` + timestamp)
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)
10+
# • Generates an SPDX SBOM with Syft
11+
# • Scans the image for CRITICAL/HIGH CVEs with Trivy
12+
# • Uploads both Dockle and Trivy results as SARIF files
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
16+
#
17+
# Triggers:
18+
# • Every push / PR to `main`
19+
# • Weekly scheduled run (Tue 18:17 UTC) to catch newly-disclosed CVEs
20+
# ---------------------------------------------------------------
21+
22+
name: Secure Docker Build
223

324
on:
425
push:
526
branches: [ "main" ]
627
pull_request:
728
branches: [ "main" ]
29+
schedule:
30+
- cron: '17 18 * * 2' # every Tuesday @ 18:17 UTC
831

9-
jobs:
10-
11-
build:
32+
# -----------------------------------------------------------------
33+
# 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+
# -----------------------------------------------------------------
39+
permissions:
40+
contents: read
41+
packages: write
42+
security-events: write
43+
actions: read
1244

45+
jobs:
46+
build-scan-sign:
1347
runs-on: ubuntu-latest
1448

49+
env:
50+
IMAGE_NAME: ghcr.io/${{ github.repository }}
51+
CACHE_DIR: /tmp/.buildx-cache # local BuildKit layer cache
52+
1553
steps:
16-
- uses: actions/checkout@v4
17-
- name: Build the Docker image using Containerfile.lite
18-
run: docker build . --file Containerfile.lite --tag mcpgateway/mcpgateway:$(date +%s) --tag mcpgateway/mcpgateway:latest
54+
# -------------------------------------------------------------
55+
# 0️⃣ Checkout source
56+
# -------------------------------------------------------------
57+
- name: ⬇️ Checkout code
58+
uses: actions/checkout@v4
59+
60+
# -------------------------------------------------------------
61+
# 1️⃣ Lint Dockerfile (Hadolint)
62+
# -------------------------------------------------------------
63+
- name: 🔍 Dockerfile lint (Hadolint)
64+
uses: hadolint/[email protected]
65+
with:
66+
dockerfile: Containerfile.lite
67+
68+
# -------------------------------------------------------------
69+
# 2️⃣ Set up Buildx & restore cache
70+
# -------------------------------------------------------------
71+
- name: 🛠️ Set up Docker Buildx
72+
uses: docker/setup-buildx-action@v3
73+
74+
- name: 🔄 Restore BuildKit layer cache
75+
uses: actions/cache@v4
76+
with:
77+
path: ${{ env.CACHE_DIR }}
78+
key: ${{ runner.os }}-buildx-${{ github.sha }}
79+
restore-keys: |
80+
${{ runner.os }}-buildx-
81+
82+
# -------------------------------------------------------------
83+
# 3️⃣ Build & tag image (timestamp + latest)
84+
# -------------------------------------------------------------
85+
- name: 🏗️ Build Docker image
86+
run: |
87+
TAG=$(date +%s)
88+
docker buildx build \
89+
--file Containerfile.lite \
90+
--tag $IMAGE_NAME:$TAG \
91+
--tag $IMAGE_NAME:latest \
92+
--cache-from type=local,src=${{ env.CACHE_DIR }} \
93+
--cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \
94+
--load
95+
96+
# -------------------------------------------------------------
97+
# 4️⃣ Lint image with Dockle
98+
# -------------------------------------------------------------
99+
- 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
108+
if: always()
109+
uses: github/codeql-action/upload-sarif@v3
110+
with:
111+
sarif_file: dockle-results.sarif
112+
113+
# -------------------------------------------------------------
114+
# 5️⃣ Generate SPDX SBOM with Syft
115+
# -------------------------------------------------------------
116+
- name: 📄 Generate SBOM (Syft)
117+
uses: anchore/sbom-action@v0
118+
with:
119+
image: ${{ env.IMAGE_NAME }}:latest
120+
output-file: sbom.spdx.json
121+
122+
# -------------------------------------------------------------
123+
# 6️⃣ Trivy CVE scan → SARIF (fails on CRITICAL/HIGH)
124+
# -------------------------------------------------------------
125+
- name: 🛡️ Trivy vulnerability scan
126+
uses: aquasecurity/trivy-action@7b7aa264d83dc58691451798b4d117d53d21edfe
127+
with:
128+
image-ref: ${{ env.IMAGE_NAME }}:latest
129+
format: template
130+
template: '@/contrib/sarif.tpl'
131+
output: trivy-results.sarif
132+
severity: CRITICAL,HIGH
133+
exit-code: 1 # break build on CRITICAL/HIGH vulns
134+
135+
- name: ☁️ Upload Trivy SARIF
136+
if: always()
137+
uses: github/codeql-action/upload-sarif@v3
138+
with:
139+
sarif_file: trivy-results.sarif
140+
141+
# -------------------------------------------------------------
142+
# 7️⃣ Push both tags to GHCR (uses built-in GITHUB_TOKEN)
143+
# -------------------------------------------------------------
144+
- name: 🔑 Log in to GHCR
145+
uses: docker/login-action@v3
146+
with:
147+
registry: ghcr.io
148+
username: ${{ github.actor }}
149+
password: ${{ secrets.GITHUB_TOKEN }}
150+
151+
- name: 🚀 Push image to GHCR
152+
run: |
153+
# Grab the timestamp tag we built earlier
154+
TIMESTAMP_TAG=$(docker images --format '{{.Tag}}' $IMAGE_NAME | grep -v latest)
155+
docker push $IMAGE_NAME:$TIMESTAMP_TAG
156+
docker push $IMAGE_NAME:latest
157+
158+
# -------------------------------------------------------------
159+
# 8️⃣ Key-less Cosign sign (OIDC) + provenance
160+
# -------------------------------------------------------------
161+
- name: 📥 Install Cosign
162+
uses: sigstore/cosign-installer@v3
163+
164+
- name: 🔏 Sign & attest image
165+
env:
166+
COSIGN_EXPERIMENTAL: "1" # enable key-less OIDC flow
167+
run: |
168+
# Cosign will interactively fetch an OIDC token from GitHub Actions
169+
cosign sign --yes $IMAGE_NAME:latest
170+
cosign attest --yes --predicate sbom.spdx.json $IMAGE_NAME:latest

0 commit comments

Comments
 (0)