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,bashtrong 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.



