Runtime 映像檔中每多一個位元組,就多一個位元組的攻擊面。理想的 production 映像檔只包含應用程式 binary、少數共享 lib,沒有 shell、沒有 package manager——而且必須被簽署,讓叢集能信任。
Multi-stage build 與 distroless base 映像檔
Go 應用程式的參考 pattern:
FROM golang:1.23 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /out/app ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /out/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]
好處:
- Runtime 映像檔 <20 MB,沒有
sh、apt、curl。 - 當攻擊者取得 RCE 時,沒有工具能用來提權或下載 payload。
- OS 層級 CVE 數量幾乎為零,降低掃描雜訊。
對於 Node.js/Python:使用 Chainguard Images、distroless/nodejs、distroless/python3。避免 :latest,一律以 digest @sha256:... 釘住。
Non-root、唯讀檔案系統、drop capabilities
在 Kubernetes 中:
securityContext:
runAsNonRoot: true
runAsUser: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
這也是 Pod Security Standards: restricted profile 的基線。如果應用程式需要寫檔 (cache、tmp),就只把 emptyDir 掛到那個路徑,而不是把整個 rootfs 開放可寫。
用 Trivy/Grype 掃描映像檔
- name: Build
run: docker build -t ghcr.io/org/app:${{ github.sha }} .
name: Scan image uses: aquasecurity/trivy-action@master with: image-ref: ghcr.io/org/app:${{ github.sha }} severity: CRITICAL,HIGH exit-code: 1 ignore-unfixed: true
ignore-unfixed: true 可避免因為尚無修補的 CVE 而 fail build——但仍寫入報告以持續追蹤。
Cosign keyless:不必管理私鑰也能簽署
Cosign keyless 依賴 OIDC identity (GitHub Actions、Google 等) 與 Fulcio 簽發的 short-lived 憑證。Workflow:
permissions:
id-token: write # 給 OIDC 用
contents: read
packages: write
-
uses: sigstore/cosign-installer@v3
-
name: Sign image env: COSIGN_EXPERIMENTAL: "true" run: cosign sign --yes ghcr.io/org/app@${{ steps.push.outputs.digest }}
name: Attach SBOM as attestation run: | syft ghcr.io/org/app@${{ steps.push.outputs.digest }} -o cyclonedx-json > sbom.json cosign attest --yes --predicate sbom.json --type cyclonedx
ghcr.io/org/app@${{ steps.push.outputs.digest }}
驗證紀錄會永久保留在 Rekor transparency log 中。沒有需要保管或輪換的私鑰。
部署前用 Kyverno 驗證
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-cosign-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["ghcr.io/org/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
只有當映像檔的簽署來自正確的 repo + workflow + branch 時,Pod 才會被建立。這是供應鏈安全的關鍵環節 (SLSA L2-L3)。
Production 映像檔檢查表
- Multi-stage、distroless/Chainguard base 映像檔,以 digest 釘住。
- USER non-root、drop ALL caps、readOnlyRootFilesystem。
- Runtime stage 不安裝
curl、wget、bash。 - 用 Trivy/Grype 掃描,有修補的 CRITICAL/HIGH 直接 fail build。
- 用 Cosign keyless 簽署 + attach CycloneDX SBOM。
- 叢集有 Kyverno verifyImages 擋下未簽署映像檔。
- HEALTHCHECK + EXPOSE 明確,不暴露未使用的 port。
結論
映像檔 hardening 是 DevSecOps 中 ROI 最高的投資之一:工夫不大,卻同時減少攻擊面與警報疲勞。當你熟悉 distroless + nonroot + Cosign keyless 的 pattern 後,每個新服務幾乎可以免費取得良好基線——而你也可以放心在整個叢集上開啟 admission policy 擋下不合規的 Pod。
