Chuyển đến nội dung chính
Bảo mật

Container image hardening: distroless, multi-stage và sign với Cosign

Một image production tốt phải nhỏ, không root, không shell, được scan và sign. Bài viết tổng hợp kỹ thuật hardening Docker/OCI image kèm workflow ký Cosign keyless với OIDC GitHub.

Container image hardening: distroless, multi-stage và sign với Cosign
Mỗi byte thừa trong image runtime là một byte attack surface. Một image production lý tưởng chỉ chứa binary ứng dụng, vài shared lib, không shell, không package manager — và phải được ký để cluster tin cậy.

Multi-stage build và base image distroless

Pattern tham khảo cho ứng dụng Go:

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"]

Lợi ích:

  • Image runtime <20 MB, không có sh, apt, curl.
  • Khi attacker đạt RCE, không có công cụ để escalate hoặc download payload.
  • Số CVE OS-level gần như bằng 0, giảm noise scan.

Với Node.js/Python: dùng Chainguard Images, distroless/nodejs, distroless/python3. Tránh :latest, luôn pin theo digest @sha256:....

Non-root, read-only filesystem, drop capabilities

Trong Kubernetes:

securityContext:
  runAsNonRoot: true
  runAsUser: 65532
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

Đây cũng là baseline của Pod Security Standards: restricted profile. Nếu app cần ghi file (cache, tmp), mount emptyDir chỉ vào path đó thay vì mở rootfs.

Scan image với 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 tránh fail build vì CVE chưa có fix — nhưng vẫn ghi vào báo cáo để theo dõi.

Cosign keyless: sign mà không quản lý private key

Cosign keyless dựa vào OIDC identity (GitHub Actions, Google, ...) và short-lived cert do Fulcio cấp. Workflow:

permissions:
  id-token: write   # cho 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 }}

Verification log lưu vĩnh viễn trên Rekor transparency log. Không có private key cần lưu/rotate.

Verify trước deploy với 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"

Pod chỉ được tạo nếu image có signature từ đúng repo + workflow + branch. Đây là một mảnh chốt của supply chain security (SLSA L2-L3).

Checklist image production

  • Multi-stage, base image distroless/Chainguard, pin digest.
  • USER non-root, drop ALL caps, readOnlyRootFilesystem.
  • Không cài curl, wget, bash trong runtime stage.
  • Quét bằng Trivy/Grype, fail build với CRITICAL/HIGH có fix.
  • Sign keyless bằng Cosign + attach SBOM CycloneDX.
  • Cluster có Kyverno verifyImages chặn image không signed.
  • HEALTHCHECK + EXPOSE rõ ràng, không expose port không dùng.

Kết luận

Hardening image là một trong những đầu tư có ROI cao nhất trong DevSecOps: ít công, giảm cả attack surface lẫn alert fatigue. Khi đã quen với pattern distroless + nonroot + Cosign keyless, mỗi service mới gần như miễn phí có baseline tốt — và bạn có thể tự tin bật admission policy chặn pod không tuân thủ trên toàn cluster.

DUY TRAN
Tác giả

DUY TRAN

Pursuing an AI-first mindset and intelligent system architecture. I build solutions by combining technology, creativity, and the ability to see structure in chaos — the foundation for becoming a Solution Architect.

Bình luận

Bài viết liên quan