Ký và Xác thực Container Image bằng Cosign (Sigstore) trong Kubernetes: Đánh giá Thực tế sau 6 Tháng Chạy Production

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Sáu tháng trước, đội ngũ platform của chúng tôi quyết định giải quyết một lỗ hổng đáng kể trong pipeline phát hành: hệ thống hoàn toàn thiếu cơ chế tự động để chứng thực rằng image đang chạy trên các cluster production thực sự đến từ pipeline CI của GitHub Actions.

Nếu ai đó đẩy một image không rõ nguồn gốc trực tiếp lên registry AWS ECR, Kubernetes vẫn sẽ thản nhiên pull về và khởi chạy. Để khắc phục lỗ hổng này trên 45 microservice với khoảng 250 lượt deploy mỗi tuần, chúng tôi đã triển khai Cosign thuộc dự án Sigstore. Dưới đây là cách việc ký container image vận hành trong thực tế, điểm mạnh, những sự cố gặp phải và cách kết hợp với admission policy.

So sánh các giải pháp ký Container Image

Để đảm bảo nguồn gốc container (provenance), thông thường có ba lựa chọn chính: Docker Content Trust (Notary v1), Notation (Notary v2) và Cosign (Sigstore). Mỗi giải pháp có cách tiếp cận khác nhau trong việc lưu trữ chữ ký, metadata và quản lý khóa.

Giải pháp Lưu trữ chữ ký Quản lý khóa Độ phức tạp khi triển khai
Docker Content Trust (Notary v1) Notary server riêng biệt và database metadata MySQL/Postgres Khóa client dài hạn (khóa TUF root/targets) Cao (Đòi hỏi tự host và scale cụm Notary server riêng)
Notation (Notary v2) OCI Registry (dưới dạng OCI Artifact referrer) Chứng chỉ PKI X.509 hoặc Cloud KMS Trung bình (Được thiết kế chuyên biệt cho hệ thống PKI doanh nghiệp)
Cosign (Sigstore) OCI Registry (manifest chữ ký đặt cùng hoặc referrer) Không dùng khóa (Keyless qua OIDC + Fulcio + Rekor), cặp khóa tĩnh hoặc KMS Thấp (Hoạt động trực tiếp trên các OCI registry tiêu chuẩn, không cần thêm database)

Docker Content Trust tỏ ra quá cồng kềnh đối với các đội ngũ cần tốc độ triển khai nhanh. Giải pháp này đòi hỏi phải dựng riêng một Notary server đi kèm cơ sở dữ liệu quan hệ. Khi phiên bản Notary đó gặp sự cố mạng hoặc lỗi vô hiệu hóa cache (cache invalidation), toàn bộ quá trình deploy lên production đều bị tắc nghẽn.

Cosign giải quyết vấn đề này bằng cách tận dụng chính container registry sẵn có làm backend lưu trữ. Khi bạn ký myregistry.com/app:v1.0.0, Cosign sẽ tải manifest chữ ký lên trực tiếp bên cạnh image đó. Không cần duy trì database bên ngoài, không cần đồng bộ thêm trạng thái nào khác.

Ưu và Nhược điểm sau 6 Tháng Triển khai Production

Việc vận hành Cosign trong môi trường quản lý hơn 1.200 pod đã mang lại cho chúng tôi những số liệu thực tế rõ ràng về thế mạnh cũng như các điểm nghẽn của công cụ này.

Ưu điểm

  • Ký không cần khóa (Keyless Signing) với OIDC: Bằng cách kết hợp token OIDC ngắn hạn từ GitHub Actions với Fulcio (CA của Sigstore) và Rekor (transparency log), chúng tôi đã loại bỏ hoàn toàn việc quản lý các khóa ký dài hạn. Lập trình viên không bao giờ phải tự tạo, xoay vòng hay lưu trữ private key.
  • Hỗ trợ đa dạng Registry: Cosign hoạt động tương thích ngay lập tức với AWS ECR, Google Artifact Registry và Harbor mà không cần cài thêm plugin hay adapter API tùy chỉnh nào.
  • Kiểm tra nhanh tại Admission Webhook: Việc xác thực chữ ký tại tầng admission của Kubernetes bằng Kyverno chỉ phát sinh độ trễ chưa tới 45ms cho các webhook tiếp nhận pod.
  • Hỗ trợ Attestation và SBOM: Cosign không chỉ xử lý digest thuần của image. Chúng tôi sử dụng cùng một workflow CLI để ký và đính kèm SBOM định dạng CycloneDX cũng như file chứng thực nguồn gốc build (provenance) theo chuẩn SLSA vào container image.

Nhược điểm

  • Rò rỉ Metadata trên Rekor công khai: Phiên bản Sigstore công khai sẽ ghi lại các thông tin build và email tác giả commit lên sổ cái công khai. Nếu công ty bạn phát triển phần mềm nguồn đóng độc quyền và không thể để lộ URL repository hay email của kỹ sư, bạn bắt buộc phải tự host toàn bộ stack Sigstore nội bộ hoặc chuyển sang dùng khóa Cloud KMS riêng.
  • Xung đột vòng đời lưu trữ (Lifecycle) trên Registry: Cosign tạo các artifact tag có định dạng sha256-[digest].sig. Hai tuần sau khi triển khai, lifecycle policy của ECR tự động dọn dẹp các base image không gắn tag, vô tình xóa mất các chữ ký liên quan và làm gián đoạn việc deploy trên môi trường staging. Chúng tôi đã phải cập nhật lại quy tắc lưu giữ trên registry để bảo vệ riêng biệt các tag *.sig và *.att.
  • Gánh nặng vận hành trong môi trường Air-Gapped: Để chạy Sigstore keyless trong một VPC cô lập hoặc trung tâm dữ liệu air-gapped hoàn toàn, bạn cần tự host Fulcio, Rekor, Trillian và một OIDC provider như Dex nội bộ. Mô hình này đòi hỏi chi phí bảo trì hệ thống đáng kể.

Kiến trúc Kubernetes đề xuất

Đối với phần lớn các đội ngũ cloud engineering, mô hình hybrid mang lại sự cân bằng tối ưu nhất giữa tính bảo mật và tính đơn giản trong vận hành:

  • Pipeline CI/CD: Ký image ngay trong quá trình build bằng xác thực keyless OIDC trên cloud CI (như GitHub Actions) hoặc Cloud KMS (AWS KMS, Google Cloud KMS, HashiCorp Vault Transit).
  • Thực thi trên Cluster: Triển khai Kyverno hoặc Gatekeeper dưới dạng validating admission webhook để chặn mọi container không có chữ ký mã hóa hợp lệ trước khi pod được lập lịch chạy.

Hướng dẫn triển khai từng bước

Dưới đây là quy trình hoàn chỉnh từ tạo khóa, ký image theo digest bất biến, cho đến thực thi kiểm tra xác thực bên trong Kubernetes.

Bước 1: Cài đặt Cosign CLI

Tải bản binary mới nhất cho build runner hoặc máy trạm của bạn:

# Tải và cài đặt binary Cosign (Linux amd64)
LATEST_VERSION=$(curl -s https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d '"' -f 4)
curl -LO "https://github.com/sigstore/cosign/releases/download/${LATEST_VERSION}/cosign-linux-amd64"
sudo install -o root -g root -m 0755 cosign-linux-amd64 /usr/local/bin/cosign

# Kiểm tra binary đã hoạt động
cosign version

Bước 2: Tạo cặp khóa mã hóa

Nếu không sử dụng phương thức ký keyless công khai, bạn có thể tạo cặp khóa có bảo vệ bằng mật khẩu:

# Tạo cosign.key (private) và cosign.pub (public)
cosign generate-key-pair

# Nhập passphrase bảo mật khi được nhắc

Lưu cosign.key vào hệ thống quản lý secret trong CI (như HashiCorp Vault hoặc AWS Secrets Manager). Giữ lại cosign.pub để nhúng vào policy trên Kubernetes.

Bước 3: Ký Container Image

Hãy luôn ký image bằng mã băm SHA256 digest bất biến thay vì dùng các tag có thể thay đổi như latest hay v1.2.0. Tag có thể bị trỏ lại sang image khác, còn digest thì không.

# Cung cấp mật khẩu khóa trong pipeline CI
export COSIGN_PASSWORD="YourProductionSecretPassword"

# Xác định chính xác digest của image
IMAGE_DIGEST="registry.example.com/production/auth-service@sha256:7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"

# Tiến hành ký image
cosign sign --key cosign.key "${IMAGE_DIGEST}"

Cosign sẽ tính toán payload chữ ký và tải trực tiếp lên repository của bạn dưới dạng một manifest layer mới.

Bước 4: Xác thực Image thủ công

Kiểm tra chữ ký từ terminal bằng public key:

cosign verify --key cosign.pub "${IMAGE_DIGEST}"

Nếu payload hợp lệ và digest trùng khớp, Cosign sẽ in ra các claim xác thực dưới định dạng JSON. Nếu image đã bị chỉnh sửa hoặc ký bởi một khóa không xác định, Cosign sẽ trả về mã thoát (exit code) khác 0.

Bước 5: Thực thi xác thực tại thời điểm tiếp nhận (Admission) với Kyverno

Việc kiểm tra thủ công trong CI không thể ngăn kỹ sư chạy trực tiếp container chưa được xác thực thông qua kubectl. Chúng tôi thực thi quy tắc kiểm tra image ngay tại ranh giới cluster bằng một ClusterPolicy của Kyverno.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: enforce-image-signature
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-trusted-signature
      match:
        any:
        - resources:
            kinds:
              - Pod
      verifyImages:
        - imageReferences:
            - "registry.example.com/production/*"
          key: |-
            -----BEGIN PUBLIC KEY-----
            MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7p9r7K9eJ8h9qK3vQ1Z5w8xL
            3oR7s+9p4qL8k7m6w5b0x2j3z5v8t1y4u7i0o3p6a9s2d5f8g1h4j7k0l3==
            -----END PUBLIC KEY-----

Áp dụng policy vào cluster của bạn:

kubectl apply -f verify-policy.yaml

Thử tạo một pod với image chưa được ký để kiểm tra khả năng chặn:

kubectl run test-pod --image=registry.example.com/production/auth-service:untrusted

Kubernetes API server sẽ từ chối yêu cầu ngay lập tức:

Lỗi từ server: admission webhook "validate.kyverno.svc" đã từ chối yêu cầu: 
ClusterPolicy enforce-image-signature thất bại: xác thực chữ ký image thất bại cho registry.example.com/production/auth-service:untrusted

Các bước tiếp theo

Việc triển khai cơ chế bắt buộc xác thực chữ ký không nhất thiết phải làm gián đoạn công việc của các nhóm phát triển. Hãy bắt đầu bằng cách triển khai admission policy ở chế độ validationFailureAction: Audit. Chế độ này giúp bạn gắn cờ và theo dõi các image chưa được ký trên dashboard giám sát mà không làm gián đoạn các đợt deploy đang diễn ra. Khi pipeline CI đã ký ổn định 100% các bản build production, hãy chuyển policy sang chế độ Enforce.

Share: