Hướng dẫn ký và xác minh Docker Image với Sigstore Cosign: Chặn đứng rủi ro Supply Chain

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Hiểm họa từ Docker Image không rõ nguồn gốc trên Production

Đi audit bảo mật cho hơn 10 hệ thống gần đây, mình thấy một nghịch lý quen thuộc. Đội ngũ kỹ thuật vá lỗi OS hàng tuần, siết chặt từng cổng firewall. Thế nhưng, container image kéo về server lại gần như bị thả nổi. Không ai kiểm tra xem file image đó có thực sự nguyên vẹn hay không.

Docker tag có tính chất mutable (dễ dàng bị ghi đè). Lỗ hổng này mở đường cho nhiều rủi ro thực tế:

  • Credential registry bị rò rỉ: Kẻ tấn công chiếm token Docker Hub hay Harbor nội bộ, đẩy đè mã độc đào coin (như XMRig) vào chính tag :v1.0.0 mà team đang chuẩn bị deploy.
  • Tấn công Man-in-the-Middle (MitM): Lưu lượng tải image qua mạng nội bộ hoặc qua proxy bị can thiệp, hoán đổi manifest giữa chừng.
  • Kéo nhầm base image trôi nổi: Dev dùng nhầm image chứa sẵn backdoor hoặc dính CVE nghiêm trọng chưa vá.

Làm sao máy chủ production phân biệt được image do chính pipeline công ty build với một bản dựng độc hại bị kẻ lạ nhét vào giữa đường? Đây chính là bài toán toàn vẹn chuỗi cung ứng phần mềm (Software Supply Chain Security).

Sigstore Cosign: Giải pháp ký số nhẹ và thực dụng

Trước đây, Docker Content Trust (Notary v1) từng là giải pháp phổ biến nhất. Thế nhưng Notary đòi hỏi server riêng, quản lý TUF metadata phức tạp và bảo trì rất tốn công. Đa phần các team DevOps nhanh chóng bỏ cuộc vì độ cồng kềnh của nó.

Cosign thuộc dự án Sigstore (do Linux Foundation bảo trợ) giải quyết vấn đề gọn gàng hơn nhiều. Cosign không dựng thêm database mới. Công cụ này tận dụng chính Container Registry hiện có (Docker Hub, GHCR, AWS ECR hay Harbor) làm nơi lưu trữ chữ ký. Khi ký, Cosign tạo một OCI artifact chứa payload chữ ký và push thẳng lên registry, gắn chặt với sha256 digest của image gốc.

Hiện tại có hai phương pháp ký chính:

  1. Dùng cặp khóa (Key-based signing): Sinh private/public key quen thuộc. Private key lưu trong secret của CI runner để ký. Public key đặt ở server đích để verify. Mô hình này rất dễ dựng cho team vừa và nhỏ.
  2. Ký phi khóa (Keyless signing): Tận dụng OpenID Connect (OIDC) qua Fulcio để cấp chứng chỉ ngắn hạn (vài phút) và ghi log vào sổ cái minh bạch Rekor. Cách này triệt tiêu hoàn toàn nỗi lo lộ private key dài hạn.

Thực hành: Ký và xác minh Docker Image từng bước

Bước 1: Cài đặt Cosign trên Linux

Tải trực tiếp bản binary chính thức từ Sigstore về máy:

# Tải binary cosign v2.x
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"

# Đổi tên và cấp quyền thực thi
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign

# Kiểm tra cài đặt
cosign version

Bước 2: Tạo cặp khóa Cosign

Chạy lệnh sinh khóa:

cosign generate-key-pair

Hệ thống sẽ yêu cầu nhập passphrase bảo vệ private key. Khi chạy tự động trên CI/CD, bạn có thể truyền sẵn biến môi trường COSIGN_PASSWORD để script không bị dừng lại chờ prompt.

Lệnh chạy xong sẽ tạo 2 file:

  • cosign.key: Private key dùng để ký. Phải bảo mật tuyệt đối, chỉ lưu trong GitHub Secrets hoặc Vault.
  • cosign.pub: Public key dùng để kiểm tra. File này an toàn để phân phối rộng rãi ra các node production.

Bước 3: Build và đẩy Image lên Registry

Lưu ý quan trọng: Cosign chỉ ký khi image đã nằm trên remote registry. Hãy đăng nhập và đẩy một test image:

docker login

# Gắn tag và push image thử nghiệm
docker tag alpine:3.19 your-dockerhub-user/secure-app:1.0.0
docker push your-dockerhub-user/secure-app:1.0.0

Bước 4: Ký số cho Image

Chạy lệnh cosign sign kèm private key:

cosign sign --key cosign.key your-dockerhub-user/secure-app:1.0.0

Nhập passphrase bạn đã đặt ở bước 2. Ngay sau đó, Cosign tự động đẩy một tag mới có tiền tố sha256-<digest>.sig lên registry của bạn để lưu kèm chữ ký.

Bước 5: Xác minh chữ ký tại Production

Đứng trên server production (nơi chỉ giữ file cosign.pub), bạn chạy lệnh kiểm tra:

cosign verify --key cosign.pub your-dockerhub-user/secure-app:1.0.0

Nếu chữ ký khớp và image còn nguyên vẹn, Cosign trả về JSON payload kèm digest:

[
  {
    "critical": {
      "identity": { "docker-reference": "index.docker.io/your-dockerhub-user/secure-app" },
      "image": { "docker-manifest-digest": "sha256:d4e3..." },
      "type": "cosign container image signature"
    }
  }
]

Thử giả lập kịch bản tấn công: Bạn build một image khác rồi push đè lên tag 1.0.0 mà không ký lại. Lệnh verify lập tức trả về mã lỗi:

Error: no matching signatures:
failed to verify signature

Bước 6: Tích hợp vào script triển khai

Để ngăn chặn việc chạy nhầm container giả mạo, hãy bọc lệnh chạy vào script kiểm tra exit code:

#!/usr/bin/env bash
set -euo pipefail

IMAGE="your-dockerhub-user/secure-app:1.0.0"
PUBLIC_KEY="/opt/security/cosign.pub"

echo "[*] Dang kiem tra tinh toan ven: ${IMAGE}"
if cosign verify --key "${PUBLIC_KEY}" "${IMAGE}" > /dev/null 2>&1; then
    echo "[+] Chu ky hop le. Dang khoi chay container..."
    docker run -d --name production-app "${IMAGE}"
else
    echo "[!] LOI: Image chua duoc ky hoac bi gia mao. Huy deploy!" >&2
    exit 1
fi

Nếu vận hành Kubernetes, bạn có thể thay thế shell script bằng Admission Controller như Kyverno hoặc OPA Gatekeeper. Chúng sẽ tự động từ chối mọi Pod cố tình kéo image chưa qua kiểm duyệt chữ ký.

Lời kết

Bảo vệ máy chủ là điều kiện cần. Kiểm soát thứ được đưa lên máy chủ mới là điều kiện đủ. Thiết lập Cosign chỉ tốn của team khoảng 15 phút, nhưng nó giúp bạn loại bỏ hoàn toàn nguy cơ chạy nhầm container độc hại trên production.

Share: