Chuyển đến nội dung chính

容器映像檔 hardening:distroless、multi-stage 與 Cosign 簽署

Duy Tran9 分鐘
容器映像檔 hardening:distroless、multi-stage 與 Cosign 簽署
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。